BuddhaNexus/buddhanexus-frontend

View on GitHub
src/views/data/data-view.js

Summary

Maintainability
D
2 days
Test Coverage
/**
 * @file data-view is the container component of table-view, numbers-view, table-view and graph-view.
 *
 * @todo:
 * - pass filter values inside an object instead of separately in order to simplify code.
 */

import { customElement, html, LitElement, property } from 'lit-element';

import '../menus/navigation-menu.js';
import '../components/side-sheet';
import '../components/card';
import { updateFileParamInBrowserLocation } from './dataViewUtils';
import './data-view-router';
import './data-view-filters-container';
import './data-view-settings-container';
import './data-view-view-selector';
import './data-view-header';

import '../utility/total-numbers';
import { getLanguageFromFilename } from '../utility/views-common';
import dataViewStyles from './data-view.styles';
import { getMainLayout, switchNavbarVisibility } from '../utility/utils';
import { DATA_VIEW_MODES } from './data-view-filters-container';
import {
  LANGUAGE_CODES,
  MIN_LENGTHS,
  DEFAULT_LENGTHS,
  DEFAULT_SCORES,
} from '../utility/constants';

@customElement('data-view')
export class DataView extends LitElement {
  @property({ type: String }) fileName = '';
  @property({ type: String }) language;
  @property({ type: Number }) score;
  @property({ type: Number }) quoteLength;
  @property({ type: Number }) minLength;
  @property({ type: Number }) cooccurance = 2000; // just put it to a high value so it is practically disabled per default.
  @property({ type: Array }) targetCollection = [];
  @property({ type: Array }) limitCollection = [];
  @property({ type: Array }) setLimitOrTargetCollection = [];
  @property({ type: String }) searchString;
  @property({ type: String }) multiSearchString;
  @property({ type: String }) transMethod = 'wylie';
  @property({ type: Boolean }) showSCEnglish;
  @property({ type: String }) sortMethod = 'position';
  @property({ type: String }) viewMode;
  @property({ type: Array }) multiLingualMode = [
    LANGUAGE_CODES.PALI,
    LANGUAGE_CODES.SANSKRIT,
    LANGUAGE_CODES.TIBETAN,
    LANGUAGE_CODES.CHINESE,
  ];
  @property({ type: String }) activeSegment;
  @property({ type: String }) folio;
  @property({ type: String }) headerVisibility = '';
  @property({ type: Boolean }) filterBarOpen;
  @property({ type: Boolean }) showSegmentNumbers;
  @property({ type: String }) segmentDisplaySide;

  static get styles() {
    return [dataViewStyles];
  }

  connectedCallback() {
    super.connectedCallback();
    this.updateViewModeParamInUrl();
    this.setFileParamFromPath();
    this.setCurrentLanguageFromPath();
  }

  firstUpdated(_changedProperties) {
    super.firstUpdated(_changedProperties);
    this.handleViewModeParamChanged();
    this.initializeFilterValues(this.language);
    this.checkSelectedView();
  }

  initializeFilterValues(lang) {
    switch (lang) {
      case LANGUAGE_CODES.TIBETAN:
        this.minLength = MIN_LENGTHS.TIBETAN;
        this.quoteLength = DEFAULT_LENGTHS.TIBETAN;
        this.score = DEFAULT_SCORES.TIBETAN;
        this.multiLingualMode = [LANGUAGE_CODES.TIBETAN];
        break;
      case LANGUAGE_CODES.PALI:
        this.minLength = MIN_LENGTHS.PALI;
        this.quoteLength = DEFAULT_LENGTHS.PALI;
        this.score = DEFAULT_SCORES.PALI;
        this.multiLingualMode = [LANGUAGE_CODES.PALI];
        break;
      case LANGUAGE_CODES.SANSKRIT:
        this.minLength = MIN_LENGTHS.SANSKRIT;
        this.quoteLength = DEFAULT_LENGTHS.SANSKRIT;
        this.score = DEFAULT_SCORES.SANSKRIT;
        //this.multiLingualMode = [LANGUAGE_CODES.SANSKRIT];
        break;
      case LANGUAGE_CODES.CHINESE:
        this.minLength = MIN_LENGTHS.CHINESE;
        this.quoteLength = DEFAULT_LENGTHS.CHINESE;
        this.score = DEFAULT_SCORES.CHINESE;
        this.multiLingualMode = [LANGUAGE_CODES.CHINESE];
        break;
      case LANGUAGE_CODES.MULTILING:
        this.score = DEFAULT_SCORES.MULTILING;
        this.minLength = MIN_LENGTHS.MULTILING;
        this.quoteLength = DEFAULT_LENGTHS.MULTILING;
        break;
    }
  }

  updated(_changedProperties) {
    this.checkSelectedView();
    _changedProperties.forEach((oldValue, propName) => {
      if (propName === 'fileName') {
        this.updateFileNameParamInUrl(this.fileName, this.activeSegment);
        this.checkSearchSelectedText();
      }
      if (propName === 'viewMode' && this.score == null) {
        // when the viewMode is changed and the filter-values are not set yet, we have to initialize them
        const tempLang = getLanguageFromFilename(this.fileName);
        this.initializeFilterValues(tempLang);
      }

      if (propName === 'language') {
        getMainLayout()
          .querySelector('navigation-menu')
          .setAttribute('language', this.language);
      }
      if (
        ['limitCollection', 'targetCollection', 'viewMode'].includes(propName)
      ) {
        this.setLimitOrTargetCollection =
          this.viewMode == 'graph'
            ? this.targetCollection
            : this.limitCollection;
      }
    });
  }

  handleViewModeParamChanged = () => {
    this.handleViewModeChanged(this.location.params.viewMode.toLowerCase());
  };

  setFileParamFromPath() {
    const { fileName, activeSegment } = this.location.params;
    if (fileName) {
      if (fileName !== this.fileName) {
        this.fileName = fileName;
        this.language = getLanguageFromFilename(fileName);
      }
      // This to revert a previous hack because dots in the segmentnumber are not accepted in the routing.
      if (activeSegment) {
        this.activeSegment = activeSegment.replace(/@/g, '.');
      }
    }
  }

  checkSelectedView() {
    if (this.viewMode === DATA_VIEW_MODES.NEUTRAL && this.fileName) {
      let newUrl;
      if (this.language != LANGUAGE_CODES.MULTILING) {
        this.viewMode = DATA_VIEW_MODES.TEXT;
        newUrl = this.location.pathname.replace('neutral', 'text');
      } else {
        this.viewMode = DATA_VIEW_MODES.MULTILING;
        newUrl = this.location.pathname.replace('neutral', 'multi');
      }
      this.location.pathname = newUrl;
      history.replaceState({}, null, newUrl);
    }
  }
  // handles the case that a new text was selected while browsing the search-results in local-search-view.
  checkSearchSelectedText() {
    if (this.viewMode === DATA_VIEW_MODES.TEXT_SEARCH && this.fileName) {
      this.viewMode = DATA_VIEW_MODES.TEXT;
    }
  }

  setCurrentLanguageFromPath() {
    this.language = this.location.pathname.split('/')[1];
  }

  setFileName = fileName => {
    // Is it the case that we might trigger a re-rendering in case the variable value is the
    // same but it get's updated anyway? I suspect this is happening, this is why I added the if-statements here.
    if (this.fileName !== fileName) {
      this.fileName = fileName;
    }
    this.multiLingualMode = [
      LANGUAGE_CODES.PALI,
      LANGUAGE_CODES.SANSKRIT,
      LANGUAGE_CODES.TIBETAN,
      LANGUAGE_CODES.CHINESE,
    ];
  };

  setSelectedView = viewName => {
    this.viewMode = viewName;
  };

  setFolio = folio => {
    if (this.folio !== folio) {
      this.folio = folio;
    }
  };

  setScore = e => {
    if (e.target.value !== this.score) {
      this.score = e.target.value;
    }
  };

  setSearch = searchString => {
    this.searchString = searchString;
    this.viewMode = DATA_VIEW_MODES.TEXT_SEARCH;
  };

  setMultiLingSearch = searchString => {
    this.multiSearchString = searchString;
  };

  setQuoteLength = e => {
    if (e.target.value !== this.quoteLength) {
      this.quoteLength = e.target.value;
    }
  };

  setCooccurance = e => {
    if (e.target.value !== this.cooccurance) {
      this.cooccurance = e.target.value;
    }
    // if the user sets the value to 30, we force it very high so all co-ocs are displayed.
    if (this.cooccurance > 29) {
      this.cooccurance = 10000;
    }
  };

  toggleTransMode = e => {
    this.transMethod = e.target.value;
  };

  setSortMethod = e => {
    this.sortMethod = e.target.value;
    if (this.sortMethod != 'position') {
      this.folio = '';
    }
  };

  setLimitCollection = limitCollection => {
    // if we don't do this check, limitCollection gets updated constantly and triggers refetching of the data which is very undesired.
    if (this.limitCollection.toString() !== limitCollection.toString()) {
      this.limitCollection = limitCollection;
      this.setLimitOrTargetCollection =
        this.viewMode == 'graph' ? this.targetCollection : this.limitCollection;
    }
  };

  setTargetCollection = targetCollection => {
    this.targetCollection = targetCollection;
  };

  updateFileNameParamInUrl(fileName, activeSegment) {
    if (!fileName) {
      return;
    }
    const { fileName: oldFileParam } = this.location.params;
    this.location.params.fileName = fileName;
    updateFileParamInBrowserLocation(!oldFileParam, fileName, activeSegment);
  }

  updateViewModeParamInUrl = newViewMode => {
    const { viewMode: viewModeParam } = this.location.params;
    this.viewMode = viewModeParam;

    if (!viewModeParam) {
      updateFileParamInBrowserLocation();
    } else if (!newViewMode) {
      return;
    } else if (viewModeParam !== newViewMode) {
      const pathParams = location.href.split('/');
      const viewModeParamIndex = pathParams.indexOf(this.language) + 1;
      pathParams[viewModeParamIndex] = newViewMode;
      const newUrl = pathParams.join('/');
      // TODO: check if history.replaceState can be replaced by Router.go
      history.replaceState({}, null, newUrl);
      this.location.pathname = newUrl;
    }
    this.location.params.viewMode = newViewMode;
  };

  handleViewModeChanged = newViewMode => {
    if (newViewMode === this.viewMode) {
      return;
    }
    this.updateViewModeParamInUrl(newViewMode);
    this.viewMode = newViewMode;
  };

  toggleFilterBarOpen = () => {
    this.filterBarOpen = !this.filterBarOpen;
  };

  toggleNavBar = () => {
    if (this.headerVisibility === '') {
      this.headerVisibility = 'no-header';
      switchNavbarVisibility(false);
    } else {
      this.headerVisibility = '';
      switchNavbarVisibility(true);
    }
  };

  toggleShowSegmentNumbers = e => {
    this.showSegmentNumbers = e.detail.value;
  };

  toggleSegmentDisplaySide = e => {
    this.segmentDisplaySide = e.target.value;
  };

  toggleShowSCTranslation = e => {
    this.showSCEnglish = e.detail.value;
  };

  setMultiLingualMode = multiLingualList => {
    this.multiLingualMode = multiLingualList;
  };

  displaySettings = () => {
    return this.viewMode == 'text' || this.viewMode == 'english'
      ? 'display: inline-flex'
      : 'display: none';
  };

  shouldShowTotalNumbers() {
    return this.viewMode != 'english';
  }

  render() {
    //prettier-ignore
    return html`
      <div class="data-view ${this.headerVisibility}" lang="${this.language}" view="${this.viewMode}">
        <div class="data-view__main-container">
          <data-view-header
            .language="${this.language}"
            .fileName="${this.fileName}"
            .setFileName="${this.setFileName}"
            .viewMode="${this.viewMode}"
            .sortMethod="${this.sortMethod}"
            .score="${this.score}"
            .handleViewModeChanged="${this.handleViewModeChanged}"
            .setFolio="${this.setFolio}"
            .filterBarOpen="${this.filterBarOpen}"
            .toggleFilterBarOpen="${this.toggleFilterBarOpen}"
            .toggleNavBar="${this.toggleNavBar}"
            .updateSearch="${this.setSearch}"
            .multiLingualMode="${this.multiLingualMode}"
            .updateMultiLingSearch="${this.setMultiLingSearch}"
            .updateSortMethod="${this.setSortMethod}"
            .toggleTransMode="${this.toggleTransMode}"
            .headerVisibility="${this.headerVisibility}">
          </data-view-header>

          <data-view-router
            .selectedView="${this.viewMode}"
            .setSelectedView="${this.setSelectedView}"
            .lang="${this.language}"
            .setFileName="${this.setFileName}"
            .fileName="${this.fileName}"
            .activeSegment="${this.activeSegment}"
            .folio="${this.folio}"
            .limitCollection="${this.limitCollection}"
            .targetCollection="${this.targetCollection}"
            .quoteLength="${this.quoteLength}"
            .cooccurance="${this.cooccurance}"
            .score="${this.score}"
            .sortMethod="${this.sortMethod}"
            .showSCEnglish="${this.showSCEnglish}"
            .transMethod="${this.transMethod}"
            .searchString="${this.searchString}"
            .multiLingualMode="${this.multiLingualMode}"
            .multiSearchString="${this.multiSearchString}"
            .headerVisibility="${this.headerVisibility}"
            .showSegmentNumbers="${this.showSegmentNumbers}"
            .segmentDisplaySide="${this.segmentDisplaySide}">
          </data-view-router>
        </div>

        <side-sheet
          id="filter-menu"
          class="${this.filterBarOpen
            ? 'side-sheet--open'
            : 'side-sheet--closed'}"
          .handleClose="${() => {
            this.filterBarOpen = false;
          }}">
          <data-view-total-numbers
            id="total-numbers"
            style="display: ${
              this.shouldShowTotalNumbers() ? 'block' : 'none'
            }"
            .fileName="${this.fileName}"
            .score="${this.score}"
            .limitCollection="${this.setLimitOrTargetCollection}"
            .quoteLength="${this.quoteLength}"
            .cooccurance="${this.cooccurance}">
          </data-view-total-numbers>

          <data-view-filters-container
            .fileName="${this.fileName}"
            .viewMode="${this.viewMode}"
            .score="${this.score}"
            .updateScore="${this.setScore}"
            .quoteLength="${this.quoteLength}"
            .minLength="${this.minLength}"
            .updateQuoteLength="${this.setQuoteLength}"
            .updateLimitCollection="${this.setLimitCollection}"
            .updateTargetCollection="${this.setTargetCollection}"
            .updateMultiLingualMode="${this.setMultiLingualMode}"
            .multiLingualMode="${this.multiLingualMode}"
            .cooccurance="${this.cooccurance}"
            .updateCooccurance="${this.setCooccurance}"
            .language="${this.language}">
          </data-view-filters-container>

          <data-view-settings-container
            class="settings-menu"
            style="${this.displaySettings()}"
            lang="${this.language}"
            view="${this.viewMode}"
            .fileName="${this.fileName}"
            .viewMode="${this.viewMode}"
            .toggleShowSegmentNumbers="${this.toggleShowSegmentNumbers}"
            .toggleSegmentDisplaySide="${this.toggleSegmentDisplaySide}"
            .toggleShowSCTranslation="${this.toggleShowSCTranslation}">
          </data-view-settings-container>
        </side-sheet>
      </div>
    `;
  }
}