Admin.js
The admin page can be accessed by clicking on Admin on the index.html. After entering the username and password (admin for both), the User can interact with the game content by interacting with the player tables. In the document web-admin.js, we have all the code used to perform these interactions.
In the file, we have one class.
class AdminController
The AdminController class contains all the methods needed to connect to the stream, display the proper information, and send the commands to the game.
Outside of the class, we create an instance of AdminController and perform a start() on it to start the connection to the stream.
let admin = new AdminController("video_player_hidden");
admin.start();
We will cover the content of the AdminController class in the following sections.
start
start()
The start method starts the connection to the services. If the
connection executes properly, we proceed to the onChannelJoin
method with the IChannelJoinResponse()
information
found. We do this process in exactly the same way in the web.js
file.
// Starts the connection to the services
start() {
fetch("/api/public/channels/join", {
method: "POST",
})
.then((data) => data.json())
.then((res) => this.onChannelJoin(res))
.catch((error) => genvid.error(`Can't get the stream info: ${error}`));
}
onChannelJoin
onChannelJoin(joinRep)
The onChannelJoin method creates the Genvid client after connecting to the
services and channel. We use the information found during this process
and the videoPlayerId sent during the class creation process to
create the client (it is an argument to the AdminController
class). We do this process in a similar way in the web.js
file,
although we do not need some methods in this case.
Afterwards, we need to associate specific events to a function in this class:
- onStreamsReceived
Triggered when the stream content is received (used to get the game data).
We then proceed to start the client with the start()
method.
// Creates the genvid Client and the function listening to it
onChannelJoin(joinRep) {
this.client = genvid.createGenvidClient(
joinRep.info,
joinRep.uri,
joinRep.token,
this.videoPlayerId
);
this.client.onStreamsReceived((streams) => {
this.onStreamsReceived(streams);
});
this.client.onNotificationsReceived((notifications) => {
this.onNotificationsReceived(notifications);
});
this.client.onAuthenticated(() => {
this.onAuthenticated()
})
this.client.start();
}
onStreamsReceived
onStreamsReceived(dataStreams)
The onStreamsReceived method performs various tasks that need constant updating.
We call it when the website receives the stream and every frame for the Genvid client we created. We do this
process in exactly the same way in the web.js
file.
We first make sure that the player table is not created.
We then need to get the game data and verify that this data is valid before doing any operation with it.
Once we are certain about the data validity, we proceed to get the object list from the data (cubes) and we convert the JSON data received.
We create a loop for each object and we create the player table for each.
We add the event listeners for clicking on the buttons of the player table.
After the loop we create two buttons for the scene and camera change.
We add event listeners for clicking the scene and camera change buttons.
onStreamsReceived(dataStreams) {
for (const stream of dataStreams.streams) {
switch (stream.id) {
case "Ability":
for (const frame of stream.frames) {
let abilities = JSON.parse(frame.data).ability;
if (abilities) {
this.canChangeCamera = abilities[0]; // will be used by changeCamera
this.canChangeScene = abilities[1]; // will be used by changeScene
}
}
if (this.canChangeCamera) {
let cameraButton = document.getElementById("cameraChange");
cameraButton.removeAttribute("disabled");
cameraButton.addEventListener(
"click",
() => {
this.changeCamera();
},
false
);
}
if (this.canChangeScene) {
let sceneButton = document.getElementById("sceneChange");
sceneButton.removeAttribute("disabled");
sceneButton.addEventListener(
"click",
() => {
this.changeScene();
},
false
);
}
break;
case "Names":
for (const frame of stream.frames) {
const cubeNames = JSON.parse(frame.data).name;
let cubeControlPanel = document.getElementById(
"admin_prototype_panel"
);
for (const name of cubeNames) {
let cubeControlPanelClone = cubeControlPanel.cloneNode(true);
cubeControlPanelClone.id = name;
cubeControlPanelClone.querySelector(
".table_name"
).innerText = name;
document
.querySelector(".admin_table_section")
.append(cubeControlPanelClone);
let upButton = cubeControlPanelClone.querySelector(
".upDirection"
);
upButton.addEventListener(
"click",
() => {
this.setDirection(name, 0, 1);
},
false
);
let downButton = cubeControlPanelClone.querySelector(
".downDirection"
);
downButton.addEventListener(
"click",
() => {
this.setDirection(name, 0, -1);
},
false
);
let leftButton = cubeControlPanelClone.querySelector(
".leftDirection"
);
leftButton.addEventListener(
"click",
() => {
this.setDirection(name, -1, 0);
},
false
);
let rightButton = cubeControlPanelClone.querySelector(
".rightDirection"
);
rightButton.addEventListener(
"click",
() => {
this.setDirection(name, 1, 0);
},
false
);
let resetButton = cubeControlPanelClone.querySelector(".reset");
resetButton.addEventListener(
"click",
() => {
this.reset(name);
},
false
);
let slowerButton = cubeControlPanelClone.querySelector(".slower");
slowerButton.addEventListener(
"click",
() => {
this.changeSpeed(name, 0.8);
},
false
);
let fasterButton = cubeControlPanelClone.querySelector(".faster");
fasterButton.addEventListener(
"click",
() => {
this.changeSpeed(name, 1.25);
},
false
);
}
cubeControlPanel.remove();
this.cubesStreamReceived = true;
}
break;
}
if (this.cubesStreamReceived) {
this.client.onStreamsReceived(null);
this.playerTableSetup = true;
}
}
}
sendCommands
sendCommands(bodyCommands, successMessage, errorMessage)
The sendCommands method sends any command to the game received from the methods in the AdminController class. It then forwards all the messages to display to the displayMessage and displayErrorMessage methods.
sendCommands(bodyCommands, successMessage, errorMessage) {
fetch("/api/admin/commands/game", {
method: "POST",
body: JSON.stringify(bodyCommands),
headers: {
"Content-Type": "application/json",
},
})
.then(() => {
this.displayMessage(successMessage);
})
.catch((err) => {
this.displayErrorMessage(`Failed with error ${err} ${errorMessage}`);
});
}
setDirection
setDirection(cubeName, x, z)
The setDirection method sends a command to the game to set the direction for a player. We call it when clicking a direction for a player.
// Changes the direction of the cube
setDirection(cubeName, x, z) {
const commands = {
id: "direction",
value: `${cubeName}:${x}:0:${z}`,
};
const successMessage = `Command sent.`;
const errorMessage = `to set direction to ${cubeName}:${x}:${z}`;
this.sendCommands(commands, successMessage, errorMessage);
}
changeSpeed
changeSpeed(cubeName, factor)
The changeSpeed method sends a command to the game to indicate that an object needs to go faster or slower. We call it when clicking on Slower or Faster for a player.
// Changes the speed of the cube
changeSpeed(cubeName, factor) {
const commands = {
id: "speed",
value: `${cubeName}:${factor}`,
};
const successMessage = `Command sent.`;
const errorMessage = `to changeSpeed ${cubeName}:${factor}`;
this.sendCommands(commands, successMessage, errorMessage);
}
reset
reset(cubeName)
The reset method sends a command to the game to set an object to its original position. We call it when clicking on Reset for a player.
// Resets the position of the cube
reset(cubeName) {
const commands = {
id: "reset",
value: cubeName,
};
const successMessage = `Command sent.`;
const errorMessage = `to reset ${cubeName}`;
this.sendCommands(commands, successMessage, errorMessage);
}
changeCamera
changeCamera()
The changeCamera method sends a command to the game to change the current camera. We call it when clicking on the Camera change button.
changeCamera() {
const commands = {
id: "camera",
value: "change",
};
const successMessage = "Camera change done";
const errorMessage = "to change the camera";
this.sendCommands(commands, successMessage, errorMessage);
}
changeScene
changeScene()
The changeScene method sends a command to the game to change the current scene. We call it when clicking on the Scene change button.
changeScene() {
const commands = {
id: "scene",
value: "change",
};
const successMessage = "Scene change done";
const errorMessage = "to change the scene";
this.sendCommands(commands, successMessage, errorMessage);
}
displayMessage
displayMessage()
The displayMessage method displays a success message about a completed operation. It’s useful for knowing if we get the data from the stream properly and to indicate if a command operation is successful.
displayMessage(message) {
let messageErrorDiv = document.querySelector("#alert_error_cube");
messageErrorDiv.style.visibility = "hidden";
let messageNotificationDiv = document.querySelector(
"#alert_notification_cube"
);
messageNotificationDiv.style.visibility = "hidden";
let messageDiv = document.querySelector("#alert_success_cube");
messageDiv.style.visibility = "visible";
let messageSpan = document.querySelector("#success_message_cube");
messageSpan.textContent = message;
}
displayErrorMessage
displayErrorMessage()
The displayErrorMessage method displays an error message about a completed operation. It’s useful for knowing if we didn’t get the data from the stream properly and to indicate if a command operation isn’t successful.
displayErrorMessage(message) {
let messageNotificationDiv = document.querySelector(
"#alert_notification_cube"
);
messageNotificationDiv.style.visibility = "hidden";
let messageDiv = document.querySelector("#alert_success_cube");
messageDiv.style.visibility = "hidden";
let messageErrorDiv = document.querySelector("#alert_error_cube");
messageErrorDiv.style.visibility = "visible";
let messageSpan = document.querySelector("#error_message_cube");
messageSpan.textContent = message;
}
sendRequestGetState
sendRequestGetState()
The sendRequestGetState method sends a request to the game received from the methods in the AdminController class. It received a response and display it in a message box.
sendRequestGetState() {
const doRequest = async () => {
const response = await fetch("/api/admin/get_state", {
method: "POST",
body: JSON.stringify({ "whoami": "admin" }),
headers: {
"Content-Type": "application/json",
},
})
const contenttype = response.headers.get('content-type');
if (!contenttype || !contenttype.includes('application/json')) {
let body = await response.text();
throw new Error(`Unexpected error for response ${response.status} ${response.statusText}: ${body}`);
}
let body = await response.json();
if (!response.ok) {
throw new Error(`Server returns ${response.status} ${response.statusText}: ${JSON.stringify(body)}`)
}
return body;
};
doRequest()
.then((data) => {
this.displayMessage(`State is ${data.value}`);
})
.catch(this.displayErrorMessage);
}