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.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.

  1. We first make sure that the player table is not created.

  2. We then need to get the game data and verify that this data is valid before doing any operation with it.

  3. 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.

  4. We create a loop for each object and we create the player table for each.

  5. We add the event listeners for clicking on the buttons of the player table.

  6. After the loop we create two buttons for the scene and camera change.

  7. 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;
  }