Home Reference Source Test Repository

src/js/ui/top-bar.js

import Dropdown from '../ui/dropdown';
import * as Form from '../ui/form';

/**
 * Class for controlling the state of the top bar. Used for displaying tool-related UI elements.
 * @class TopBar
 */
class TopBar {

  ui;

  topBar;

  modeSelect;

  dropdown;

  inputForm;
  modeInputForm;

  currentMode;
  currentModeElement;

  /**
   * Constructs a TopBar instance.
   * @param  {UI} ui - Reference to the UI instance.
   * @constructs TopBar
   */
  constructor(ui) {
    this.ui = ui;
    this.topBar = document.getElementById('top-bar');
    this.modeSelect = document.getElementById('mode-select');
    this.inputForm = document.getElementById('tool-inputs-form');
    this.modeInputForm = document.getElementById('tool-mode-inputs-form');
    this.dropdown = new Dropdown(this.modeSelect);
    this.initListeners();
  }

  /**
   * Initialize the event listeners.
   */
  initListeners() {
    this.modeSelect.addEventListener('change', (event) => {
      this.ui.toolbar.currentTool.changeMode(event.target.value);
    });

    this.inputForm.addEventListener('input', (event) => {
      this.updateInputValues(event);
    });

    this.inputForm.addEventListener('change', (event) => {
      this.updateInputValues(event);
    });

    this.modeInputForm.addEventListener('input', (event) => {
      this.updateModeInputValues(event);
    });

    this.modeInputForm.addEventListener('input', (event) => {
      this.updateModeInputValues(event);
    });

    this.modeInputForm.addEventListener('click', (event) => {
      if (event.target.classList.contains('input-select-btn')) {
        this.selectGraphObject(event);
      }
      if (event.target.classList.contains('form__submit-btn')) {
        this.submitModeInputValues(event);
      }
    });
  }

  /**
   * Toggle graph object selection. This is used for tool inputs that request a graph object.
   * @param  {Event} event - Event object from the event listener.
   */
  selectGraphObject(event) {
    let currentTool = this.ui.toolbar.currentTool;
    let output = event.target.parentNode.querySelector('output');
    let input = output.parentNode.querySelector('input[type="hidden"]');

    if (currentTool.currentInput === input) {
      currentTool.currentInput = null;
      if (input.value) {
        event.target.textContent = 'Change';
      } else {
        event.target.textContent = 'Select';
      }
    } else {
      currentTool.currentInput = input;
      event.target.textContent = 'Cancel';
    }
  }

  /**
   * Update the graph input after object selection.
   * @param  {Element} input - The input element.
   * @param  {(Node|Edge|Label)} obj - The selected graph object.
   * @param  {boolean} hasError - Whether or not the input has an error.
   */
  updateGraphInput(input, obj, hasError) {
    let currentTool = this.ui.toolbar.currentTool;
    if (hasError) {
      currentTool.currentInput = null;
      return;
    }

    let id = obj.id;
    input.value = id;

    let name = input.name;
    let output = input.parentNode.querySelector(`output[name="${name}"]`);
    output.value = `${input.getAttribute('data-type')} ${id}`;

    let button = input.parentNode.querySelector('.input-select-btn');
    button.textContent = 'Change';

    currentTool.currentInput = null;
  }

  /**
   * Handles a change in tool inputs.
   * @param  {Event} event - Event object from the event listener.
   */
  updateInputValues(event) {
    let input = event.target;
    let name = input.name;
    let value = Form.getInputValue(input);
    this.ui.toolbar.currentTool.inputs[name] = value;
    this.dropdown.updateOptionContent();
  }

  /**
   * Handles a change in mode-specific tool inputs.
   * @param  {Event} event - Event object from the event listener.
   */
  updateModeInputValues(event) {
    let currentTool = this.ui.toolbar.currentTool;
    if (!currentTool.submitModeInputs()) {
      let input = event.target;
      let name = input.name;
      let value = Form.getInputValueLocal(input, this.ui.graph);
      currentTool.modeInputs[name] = value;
    }
  }

  /**
   * Handles mode-specific tool input submission.
   * @param  {Event} event - Event object from the event listener.
   */
  submitModeInputValues(event) {
    let currentTool = this.ui.toolbar.currentTool;
    if (currentTool.submitModeInputs()) {
      let form = this.modeInputForm;
      let data = Form.getData(form, this.ui.graph);

      let hasError = false;

      for (let field of currentTool.modeInputTypes) {
        let name = field.name;
        let showError = !field.test(data[name]);
        Form.displayError(form, name, showError);

        if (showError && !hasError) {
          hasError = true;
        }
      }

      if (hasError) {
        return;
      }

      this.updateAllModeInputValues(data);
      currentTool.submitModeInputsCallback();
    }
  }

  /**
   * Update the stored values of all mode-specific tool inputs.
   * @param  {Object.<string,*>} inputData - Object containing names of inputs mapped to their values.
   */
  updateAllModeInputValues(inputData) {
    let currentTool = this.ui.toolbar.currentTool;
    for (let name of Object.keys(inputData)) {
      if (name in currentTool.modeInputs) {
        let value = inputData[name];
        currentTool.modeInputs[name] = value;
      }
    }
  }

  /**
   * Displays the tool modes in the Dropdown.
   */
  showModes() {
    let currentTool = this.ui.toolbar.currentTool;
    if (currentTool.hasModes()) {
      // populate modes list
      let html = '';
      for (let mode of Object.keys(currentTool.constructor.modes)) {
        let selected = mode === currentTool.currentMode ? ' selected="true"' : '';
        html += `<option value="${mode}" ${selected}>${mode}</option>`;
      }

      this.dropdown.optionMap = currentTool.optionMap;
      this.dropdown.optionContent = currentTool.optionContent;
      this.modeSelect.innerHTML = html;
    } else {
      // clear the modes
      this.modeSelect.innerHTML = '';
    }
  }

  /**
   * Displays the tool inputs in the inputForm element.
   */
  showInputs() {
    let currentTool = this.ui.toolbar.currentTool;
    if (currentTool.hasInputs()) {
      let inputTypes = Object.create(currentTool.inputTypes);
      let inputs = currentTool.inputs;
      for (let field of inputTypes) {
        let name = field.name;
        field.value = inputs[name];
      }
      let formContent = Form.createForm(inputTypes);
      this.inputForm.innerHTML = formContent;
    } else {
      this.inputForm.innerHTML = '';
    }
  }

  /**
   * Displays the mode-specific tool inputs in the modeInputForm element.
   */
  showModeInputs() {
    let currentTool = this.ui.toolbar.currentTool;
    if (currentTool.hasModeInputs()) {
      let inputTypes = [];
      let inputs = currentTool.modeInputs;
      for (let fieldType of currentTool.modeInputTypes) {
        let field = Object.create(fieldType);
        let name = field.name;
        field.value = inputs[name];
        inputTypes.push(field);
      }
      let formContent = Form.createGraphForm(inputTypes, currentTool.submitModeInputs(), currentTool.submitModeInputsText());
      this.modeInputForm.innerHTML = formContent;
    } else {
      this.modeInputForm.innerHTML = '';
    }
  }
}

export { TopBar };
export default TopBar;