Source: components/VoxelManager.jsx

import React from "react";
import GUIController from "./GUIController";
import VoxelEditor from "../modules/VoxelsEditor";

/**
 * The main driving component for the application. Sets up the rest of
 * the user interface and directly communicates with the Voxel.js module.
 * @extends React.Component
 */
class VoxelManager extends React.Component {
  constructor(props) {
    super(props);

    // The VoxelEditor itself that handles the 3D scene
    this.voxelEditor = null;

    // Create object with callbacks for each component
    this.callbacks = this.getCallbacksObject();
  }

  /**
   * Takes the given canvas Ref and renders the voxel world.
   * @function
   * @param {Ref} canvasRef
   */
  createVoxelWorld = (canvasRef) => {
    if (!canvasRef) return;

    if (!this.voxelEditor) {
      this.voxelEditor = new VoxelEditor({ canvas: canvasRef.current });
    } else {
      const world = this.voxelEditor.world;
      this.voxelEditor = new VoxelEditor({ canvas: canvasRef.current, world });
    }
  };

  /**
   * Changes the brush action currently being used.
   * @function
   * @param {string} brushAction - brush action to set
   */
  setBrushAction = (brushAction) => {
    if (this.voxelEditor) {
      this.voxelEditor.brush.setCurrentAction(brushAction);
    }
  };

  /**
   * Handler for when Brush components want to know the current
   * brush action in the editor.
   * @returns {string} The current brush action
   */
  onGetBrushAction = () => {
    if (!this.voxelEditor) {
      return "add";
    }

    return this.voxelEditor.brush.getCurrentAction();
  };

  /**
   * Changes the brush type currently being used.
   * @function
   * @param {string} brushType
   */
  setBrushType = (brushType) => {
    if (this.voxelEditor) {
      this.voxelEditor.brush.setCurrentType(brushType);
    }
  };

  /**
   * Handler for when Brush components want to know the current
   * brush type in the editor.
   * @returns {string} The current brush type
   */
  onGetBrushType = () => {
    if (!this.voxelEditor) {
      return "single";
    }

    return this.voxelEditor.brush.getCurrentType();
  };

  /**
   * Returns color palette data from the VoxelWorld.
   * @function
   * @returns {Array.Color}
   */
  onGetColorData = () => {
    // Return empty array if voxelEditor not ready
    if (!this.voxelEditor) {
      return {
        colors: [],
        selectedColorIndex: 0,
        currentColor: { r: 127.5, g: 127.5, b: 127.5 }, // default to a grey color
        isColorsFull: true,
      };
    }

    const { colorPalette } = this.voxelEditor.world;
    const { r, g, b } = colorPalette.getSelectedColor().getRGB255();
    return {
      colors: colorPalette.getColorsArray(),
      selectedColorIndex: colorPalette.getSelectedColorIndex(),
      currentColor: { r, g, b },
      isColorsFull: colorPalette.isColorsFull(),
    };
  };

  /**
   * Called whenever a new color is selected.
   * @function
   * @param {number} index - Index of the changed color
   * @param {Object} color
   */
  onSelectedColorChange = (index, color) => {
    if (this.voxelEditor) {
      const { r, g, b } = color;

      // Adjust the color to be on a 0-1 range
      this.voxelEditor.onSelectedColorChange(index, r / 255, g / 255, b / 255);
    }
  };

  /**
   * Tells the VoxelEditor what color of voxel the user is placing/painting now.
   * @function
   * @param {number} index
   */
  onNewSelectedColor = (index) => {
    if (this.voxelEditor) {
      this.voxelEditor.onNewSelectedColor(index);
    }
  };

  /**
   * Tell the VoxelEditor that the user added a new color to their color palette
   * @function
   */
  onAddColor = () => {
    if (!this.voxelEditor) return;

    // Add a new color to the color palette
    this.voxelEditor.world.colorPalette.addColor();
  };

  /**
   * Gets project data from the currently open project.
   * @function
   * @returns {Object} JavaScript object representing the relevant data from the
   * currently open project/scene.
   */
  onGetProjectData = () => {
    // If voxelEditor isn't ready, return an empty object
    if (!this.voxelEditor) return {};

    // Return object representing the currently open project
    return this.voxelEditor.onGetProjectData();
  };

  /**
   * Handler used to load a new scene from the given project data.
   * @function
   * @param {Object} projectData
   */
  onLoadProjectData = (projectData) => {
    // If voxelEditor isn't ready, do nothing
    if (!this.voxelEditor) return;

    // Load the project
    this.voxelEditor.onLoadProjectData(projectData);

    // @TODO: Generally, you should never invoke this method.
    // Since loading a project leads to a substantial change in the application's
    // internal state, I am making an exception.
    this.forceUpdate();
  };

  /**
   * Handler used to export the current frame from the canvas as an image.
   * @function
   * @param {string} imageName - What to name the exported image
   * @returns {Canvas} The canvas to take a screenshot from
   */
  onExportImage = (imageName) => {
    if (!this.voxelEditor) return null;

    this.voxelEditor.onExportImage(imageName);
  };

  /**
   * Exports the voxel model to some 3D file format.
   * @function
   * @param {string} name - What the exported file should be called
   * @param {string} type - The type of file to export
   */
  onExportModel = (name, type) => {
    if (!this.voxelEditor) return;

    this.voxelEditor.onExportModel(name, type);
  };

  /**
   * Loads a fresh, blank project.
   * @function
   */
  onNewProject = () => {
    if (!this.voxelEditor) return;

    this.voxelEditor.onNewProject();
    this.forceUpdate();
  };

  /**
   * Returns callbacks organized by the component that they are meant for.
   * @function
   * @returns {Object}
   */
  getCallbacksObject = () => {
    return {
      brush: {
        brushActions: {
          onBrushActionChange: this.setBrushAction,
          onGetBrushAction: this.onGetBrushAction,
        },
        brushTypes: {
          onBrushTypeChange: this.setBrushType,
          onGetBrushType: this.onGetBrushType,
        },
      },
      colorPalette: {
        onGetColorData: this.onGetColorData,
        onSelectedColorChange: this.onSelectedColorChange,
        onNewSelectedColor: this.onNewSelectedColor,
        onAddColor: this.onAddColor,
      },
      viewport: {
        onCanvasCreation: this.createVoxelWorld,
      },
      file: {
        onGetProjectData: this.onGetProjectData,
        onLoadProjectData: this.onLoadProjectData,
        onExportModel: this.onExportModel,
        onNewProject: this.onNewProject,
      },
      render: {
        onExportImage: this.onExportImage,
      },
      examples: {
        onLoadProjectData: this.onLoadProjectData,
      },
    };
  };

  render() {
    return <GUIController callbacks={this.callbacks} />;
  }
}

export default VoxelManager;