locomotivecms/engine

View on GitHub
app/javascript/src/locomotive/editor/hoc/with_editing_section.js

Summary

Maintainability
C
1 day
Test Coverage
import React from 'react';
import { Redirect } from 'react-router-dom';
import { find, pick, bindAll } from 'lodash';
import { buildClickEvent } from '../utils/misc';

// Services
import { fetchSectionContent, findBetterText as findBetterTextForSection } from '../services/sections_service';
import { fetchBlockContent, findBetterText as findBetterTextForBlock } from '../services/blocks_service';

// Acts as a controller when editing a section or block
// In charge of find the section and the block based on the location
const withEditingSection = Component => {

  const findSetting = (sectionDefinition, blockDefinition, settingId) => {
    if (settingId === null || settingId === undefined) return null;

    const settings = (blockDefinition || sectionDefinition).settings;

    return find(settings, setting => setting.id === settingId);
  }

  const findSettingLabel = (sectionDefinition, blockDefinition, settingId) => {
    const setting = findSetting(sectionDefinition, blockDefinition, settingId)
    return setting ? setting.label : null;
  }

  // Enhance the props
  const buildNewProps = props => {
    var newProps = pick(props.match.params, [
      'sectionId', 'blockType', 'blockId', 'settingType', 'settingId'
    ]);

    // 1. find the section
    newProps.section = props.sections.all[newProps.sectionId];

    if (newProps.section) {
      // 2. find the section definition and the content of the section
      newProps.sectionDefinition  = props.findSectionDefinition(newProps.section.type);
      newProps.sectionContent     = fetchSectionContent(props.globalContent, newProps.section);
      newProps.sectionLabel       = findBetterTextForSection(newProps.sectionContent, newProps.sectionDefinition);

      // 3. (if possible) find the block definition and the content of the block
      newProps.blockDefinition    = props.findBlockDefinition(
        newProps.sectionDefinition,
        newProps.blockType
      )
      newProps.blockContent       = fetchBlockContent(newProps.sectionContent, newProps.blockId);
      newProps.blockLabel         = findBetterTextForBlock(newProps.blockContent, newProps.blockDefinition);

      newProps.currentContent     = newProps.blockContent || newProps.sectionContent;

      newProps.setting            = findSetting(newProps.sectionDefinition, newProps.blockDefinition, newProps.settingId);
      newProps.settingLabel       = findSettingLabel(newProps.sectionDefinition, newProps.blockDefinition, newProps.settingId);
    }

    return Object.assign({}, props, newProps);
  }

  // Helpers
  const isEditingSection  = props => !!(props.sectionId && props.sectionContent)
  const isEditingBlock    = props => !!(props.blockId && props.blockContent)
  const isEditingSetting  = props => props.settingType && props.settingId && !props.location.pathname.endsWith('/focus')
  const isEditing         = props => isEditingSection(props) && (props.blockId === undefined || isEditingBlock(props))
  const editingType = props => {
    if (isEditingSetting(props) && isEditingBlock(props))   return 'block_setting';
    if (isEditingSetting(props) && isEditingSection(props)) return 'section_setting';
    if (isEditingBlock(props))    return 'block';
    if (isEditingSection(props))  return 'section';
    return null;
  }

  // Notifications

  const notifyOnEnter = props => {
    props.selectSection(props.section, props.blockId);
  }

  const notifyOnLeave = props => {
    props.deselectSection(props.section, props.blockId);
  }

  // Path helper

  const parentPath = props => {
    switch(editingType(props)) {
      case 'section':
        return props.rootPath();
      case 'section_setting':
      case 'block':
        return props.editSectionPath(props.section);
      case 'block_setting':
        return props.editBlockPath(props.section, props.blockType, props.blockId);
    }
  }

  // Callbacks

  const handleChange = (props, settingType, settingId, newValue) => {
    const { section, blockId, updateSectionInput } = props;
    updateSectionInput(section, blockId, settingType, settingId, newValue);
  }

  class WrappedComponent extends React.Component {

    constructor(props) {
      super(props);
      bindAll(this, 'leaveView', 'handleChange');
    }

    componentDidMount() {
      if (isEditing(this.props)) {
        notifyOnEnter(this.props);
      } else {
        // unknown section and/or block, go back to the root view
        this.props.redirectTo(this.props.rootPath());
      }
    }    

    componentDidUpdate() {
      this.applyFocus(this.props.focusedSettingId);
    }

    leaveView() {
      notifyOnLeave(this.props);
      this.props.redirectTo(parentPath(this.props));
    }

    handleChange(settingType, settingId, newValue) {
      handleChange(this.props, settingType, settingId, newValue);
    }

    applyFocus(settingId) {
      // NOTE: only support the text setting type
      if (!settingId) return;
      const label = document.querySelector(`label[for=setting-text-${settingId}]`);
      label.dispatchEvent(buildClickEvent());
      label.scrollIntoView();
      this.props.resetFocusSetting();      
    }

    render() {
      return isEditing(this.props) ? (
        <Component
          leaveView={this.leaveView}
          handleChange={this.handleChange}
          {...this.props}
        />
      ) : null;
    }

  }

  return function WithEditingSection(props) {
    const newProps = buildNewProps(props);
    return <WrappedComponent {...newProps} />
  }

}

export default withEditingSection;