Source: Main.js

/**
 * Manages the application state and instantiates the application.
 * @module Main
 */

import { Timer } from "./modules/Timer.js";
import { Score } from "./modules/Score.js";
import { Logger } from "./modules/Logger.js";
import { Target } from "./modules/Target.js";
import { Items } from "./modules/Items.js";

"use strict";

/**
 * Defines a single setter to be trapped by a Proxy for communicating component states.
 */
class Message {
  /**
   * The most important method in the application. Does nothing directly.
   * @method
   * @param {number|string|boolean|Object} message - A message to notify application components of.
   */
  set send(message) {
    //console.log("hello");
  }
}

/**
 * The main application class, which manages application state, communicates application state
 * between component classes, and initializes component classes.
 */
class Main {
  /**
   * Holds the states of application components as processed by the message proxy.
   */
  state = {};

  /**
   * Instantiates the method class, creates a proxy for the message instance, defines a setter trap
   * for the message proxy, then sets the class components property.
   * @param {...Object} components - The classes for which to pass the message proxy into and
   * conditionally initialize. The order in which they are passed is meaningful, depending on which
   * component states a given class depends on for its reactive behavior.
   */
  constructor(components) {
    const message = new Message();
    this.messageProxy = new Proxy(message, {
      set: (object, property, value) => {
        this.introspect(object, property, value);
        // Unpack the set value
        const [_object, _property, _value] = value;

        // Set the state of the sender's property
        this.state[_object].state[_property] = _value;
        // Notify application components of the message
        this.notify(
          this.state[_object].instance,
          _property,
          this.state[_object].state[_property],
        );

        return Reflect.set(object, property, value);
      },
    });

    components.forEach((Component) => {
      this.components = Component;
    });
  }

  /**
   * Sets the initial application state for the provided component.
   * @param {Function} object - The component class for which to set the state.
   */
  set components(object) {
    // Create a property for the object in the application state
    this.state[object] = {
      instance: new object(this.messageProxy),
      state: {},
    };

    const _object = this.state[object].instance;

    // If possible, initialize the component
    if (_object.hasOwnProperty("initialize") && typeof(_object.initialize) === "function") {
      _object.initialize();
    }
  }

  /**
   * For debugging and monitoring message proxy setter behavior.
   * @param {Object} object - The setter sender.
   * @param {string} property - The message property set.
   * @param {Object} value - An object containing the sender component, that component's property to
   * notify other components about, and value related to that property.
   */
  introspect(object, property, value) {
    // console.log(value);
  }

  /**
   * Calls the receive method for all application components.
   * @param {Object} object - The sender component.
   * @param {string} property - The name of the sender property.
   * @param {number|string|boolean|Object} value - The value of the sender property.
   */
  notify(object, property, value) {
    for (const _object in this.state) {
      this.state[_object].instance.receive(object, property, value);
    }
  }
}

new Main([Score, Target, Logger, Timer, Items]);