200ok-ch/organice

View on GitHub
src/components/OrgFile/components/ActionDrawer/index.js

Summary

Maintainability
F
3 days
Test Coverage
import React, { Fragment, useState, useMemo, useRef } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { Motion, spring } from 'react-motion';

import './stylesheet.css';

import { List, Map } from 'immutable';

import * as orgActions from '../../../../actions/org';
import * as captureActions from '../../../../actions/capture';
import * as baseActions from '../../../../actions/base';

import sampleCaptureTemplates from '../../../../lib/sample_capture_templates';

import ActionButton from './components/ActionButton/';
import { determineIncludedFiles } from '../../../../reducers/org';

const ActionDrawer = ({
  org,
  selectedHeaderId,
  base,
  staticFile,
  captureTemplates,
  path,
  selectedTableCellId,
  selectedListItemId,
  isLoading,
  online,
  shouldDisableSyncButtons,
  activeClocks,
}) => {
  const [isDisplayingArrowButtons, setIsDisplayingArrowButtons] = useState(false);
  const [isDisplayingCaptureButtons, setIsDisplayingCaptureButtons] = useState(false);

  const mainArrowButton = useRef(null);

  const mainArrowButtonBoundingRect = useMemo(
    () => (!!mainArrowButton.current ? mainArrowButton.current.getBoundingClientRect() : null),
    [mainArrowButton]
  );

  const handleUpClick = () =>
    !!selectedHeaderId
      ? org.moveHeaderUp(selectedHeaderId)
      : !!selectedTableCellId
      ? org.moveTableRowUp()
      : org.moveListItemUp();

  const handleDownClick = () =>
    !!selectedHeaderId
      ? org.moveHeaderDown(selectedHeaderId)
      : !!selectedTableCellId
      ? org.moveTableRowDown()
      : org.moveListItemDown();

  const handleLeftClick = () =>
    !!selectedHeaderId
      ? org.moveHeaderLeft(selectedHeaderId)
      : !!selectedTableCellId
      ? org.moveTableColumnLeft()
      : org.moveListItemLeft();

  const handleRightClick = () =>
    !!selectedHeaderId
      ? org.moveHeaderRight(selectedHeaderId)
      : !!selectedTableCellId
      ? org.moveTableColumnRight()
      : org.moveListItemRight();

  const handleMoveSubtreeLeftClick = () =>
    !!selectedHeaderId ? org.moveSubtreeLeft(selectedHeaderId) : org.moveListSubtreeLeft();

  const handleMoveSubtreeRightClick = () =>
    !!selectedHeaderId ? org.moveSubtreeRight(selectedHeaderId) : org.moveListSubtreeRight();

  const handleCaptureButtonClick = (templateId) => () => {
    setIsDisplayingCaptureButtons(false);
    base.activatePopup('capture', { templateId });
  };

  const getSampleCaptureTemplates = () => sampleCaptureTemplates;

  const getAvailableCaptureTemplates = () =>
    staticFile === 'sample'
      ? getSampleCaptureTemplates()
      : captureTemplates.filter(
          (template) =>
            template.get('isAvailableInAllOrgFiles') ||
            template
              .get('orgFilesWhereAvailable')
              .map((availablePath) =>
                availablePath.trim().startsWith('/')
                  ? availablePath.trim()
                  : '/' + availablePath.trim()
              )
              .includes((path || '').trim())
        );

  const handleSync = () => org.sync({ forceAction: 'manual' });

  const handleMainArrowButtonClick = () => setIsDisplayingArrowButtons(!isDisplayingArrowButtons);

  const handleSearchButtonClick = () => {
    base.activatePopup('search');
  };

  const handleMainCaptureButtonClick = () => {
    if (!isDisplayingCaptureButtons && getAvailableCaptureTemplates().size === 0) {
      alert(
        `You don't have any capture templates set up for this file! Add some in Settings > Capture Templates`
      );
      return;
    }

    setIsDisplayingCaptureButtons(!isDisplayingCaptureButtons);
  };

  const renderCaptureButtons = () => {
    const availableCaptureTemplates = getAvailableCaptureTemplates();

    const baseCaptureButtonStyle = {
      position: 'absolute',
      zIndex: 0,
      left: 0,
      opacity: isDisplayingArrowButtons ? 0 : 1,
    };
    if (!isDisplayingCaptureButtons) {
      baseCaptureButtonStyle.boxShadow = 'none';
    }

    const mainButtonStyle = {
      opacity: isDisplayingArrowButtons ? 0 : 1,
      position: 'relative',
      zIndex: 1,
    };

    const animatedStyle = {
      bottom: spring(isDisplayingCaptureButtons ? 70 : 0, { stiffness: 300 }),
    };

    return (
      <Motion style={animatedStyle}>
        {(style) => (
          <div className="action-drawer__capture-buttons-container">
            <ActionButton
              iconName={isDisplayingCaptureButtons ? 'times' : 'plus'}
              isDisabled={false}
              onClick={handleMainCaptureButtonClick}
              style={mainButtonStyle}
              tooltip={
                isDisplayingCaptureButtons ? 'Hide capture templates' : 'Show capture templates'
              }
            />

            {availableCaptureTemplates.map((template, index) => (
              <ActionButton
                key={template.get('id')}
                letter={template.get('letter')}
                iconName={template.get('iconName')}
                isDisabled={false}
                onClick={handleCaptureButtonClick(template.get('id'))}
                style={{ ...baseCaptureButtonStyle, bottom: style.bottom * (index + 1) }}
                tooltip={`Activate "${template.get('description')}" capture template`}
              />
            ))}
          </div>
        )}
      </Motion>
    );
  };

  const renderMovementButtons = () => {
    const baseArrowButtonStyle = {
      opacity: isDisplayingCaptureButtons ? 0 : 1,
    };
    if (!isDisplayingArrowButtons) {
      baseArrowButtonStyle.boxShadow = 'none';
    }

    let centerXOffset = 0;
    if (!!mainArrowButtonBoundingRect) {
      centerXOffset =
        window.screen.width / 2 -
        (mainArrowButtonBoundingRect.x + mainArrowButtonBoundingRect.width / 2);
    }

    const animatedStyles = {
      centerXOffset: spring(isDisplayingArrowButtons ? centerXOffset : 0, { stiffness: 300 }),
      topRowYOffset: spring(isDisplayingArrowButtons ? 150 : 0, { stiffness: 300 }),
      bottomRowYOffset: spring(isDisplayingArrowButtons ? 80 : 0, { stiffness: 300 }),
      firstColumnXOffset: spring(isDisplayingArrowButtons ? 70 : 0, {
        stiffness: 300,
      }),
      secondColumnXOffset: spring(isDisplayingArrowButtons ? 140 : 0, {
        stiffness: 300,
      }),
    };

    let subIconNameStr = null;
    let tooltipUpStr = 'Move header up';
    let tooltipDownStr = 'Move header down';
    let tooltipLeftStr = 'Move header left';
    let tooltipRightStr = 'Move header right';
    if (!!selectedTableCellId) {
      subIconNameStr = 'table';
      tooltipUpStr = 'Move row up';
      tooltipDownStr = 'Move row down';
      tooltipLeftStr = 'Move column left';
      tooltipRightStr = 'Move column right';
    } else if (!!selectedListItemId) {
      subIconNameStr = 'list';
      tooltipUpStr = 'Move list up';
      tooltipDownStr = 'Move list down';
      tooltipLeftStr = 'Move list left';
      tooltipRightStr = 'Move list right';
    }

    return (
      <Motion style={animatedStyles}>
        {(style) => (
          <div
            className="action-drawer__arrow-buttons-container"
            style={{ left: style.centerXOffset }}
          >
            <ActionButton
              additionalClassName="action-drawer__arrow-button"
              iconName="arrow-up"
              subIconName={subIconNameStr}
              isDisabled={false}
              onClick={handleUpClick}
              style={{ ...baseArrowButtonStyle, bottom: style.topRowYOffset }}
              tooltip={tooltipUpStr}
            />
            <ActionButton
              additionalClassName="action-drawer__arrow-button"
              iconName="arrow-down"
              subIconName={subIconNameStr}
              isDisabled={false}
              onClick={handleDownClick}
              style={{ ...baseArrowButtonStyle, bottom: style.bottomRowYOffset }}
              tooltip={tooltipDownStr}
            />
            <ActionButton
              additionalClassName="action-drawer__arrow-button"
              iconName="arrow-left"
              subIconName={subIconNameStr}
              isDisabled={false}
              onClick={handleLeftClick}
              style={{
                ...baseArrowButtonStyle,
                bottom: style.bottomRowYOffset,
                right: style.firstColumnXOffset,
              }}
              tooltip={tooltipLeftStr}
            />
            <ActionButton
              additionalClassName="action-drawer__arrow-button"
              iconName="arrow-right"
              subIconName={subIconNameStr}
              isDisabled={false}
              onClick={handleRightClick}
              style={{
                ...baseArrowButtonStyle,
                bottom: style.bottomRowYOffset,
                left: style.firstColumnXOffset,
              }}
              tooltip={tooltipRightStr}
            />
            {!selectedTableCellId && (
              <Fragment>
                <ActionButton
                  additionalClassName="action-drawer__arrow-button"
                  iconName="chevron-left"
                  subIconName={subIconNameStr}
                  isDisabled={false}
                  onClick={handleMoveSubtreeLeftClick}
                  style={{
                    ...baseArrowButtonStyle,
                    bottom: style.bottomRowYOffset,
                    right: style.secondColumnXOffset,
                  }}
                  tooltip="Move entire subtree left"
                />
                <ActionButton
                  additionalClassName="action-drawer__arrow-button"
                  iconName="chevron-right"
                  subIconName={subIconNameStr}
                  isDisabled={false}
                  onClick={handleMoveSubtreeRightClick}
                  style={{
                    ...baseArrowButtonStyle,
                    bottom: style.bottomRowYOffset,
                    left: style.secondColumnXOffset,
                  }}
                  tooltip="Move entire subtree right"
                />
              </Fragment>
            )}

            <ActionButton
              iconName={isDisplayingArrowButtons ? 'times' : 'arrows-alt'}
              subIconName={subIconNameStr}
              additionalClassName="action-drawer__main-arrow-button"
              isDisabled={false}
              onClick={handleMainArrowButtonClick}
              style={{ opacity: isDisplayingCaptureButtons ? 0 : 1 }}
              tooltip={isDisplayingArrowButtons ? 'Hide movement buttons' : 'Show movement buttons'}
              onRef={mainArrowButton}
            />
          </div>
        )}
      </Motion>
    );
  };

  const handleAgendaClick = () => base.activatePopup('agenda');

  return (
    <div className="action-drawer-container nice-scroll">
      {
        <Fragment>
          <ActionButton
            iconName="cloud"
            subIconName="sync-alt"
            shouldSpinSubIcon={isLoading}
            isDisabled={shouldDisableSyncButtons || !online}
            onClick={handleSync}
            style={{
              opacity: isDisplayingArrowButtons || isDisplayingCaptureButtons ? 0 : 1,
            }}
            tooltip="Sync changes"
          />

          <ActionButton
            iconName="calendar-alt"
            isDisabled={false}
            onClick={handleAgendaClick}
            style={{
              opacity: isDisplayingArrowButtons || isDisplayingCaptureButtons ? 0 : 1,
            }}
            tooltip="Show agenda"
          />

          {renderMovementButtons()}

          <ActionButton
            iconName={'search'}
            isDisabled={false}
            onClick={handleSearchButtonClick}
            additionalClassName={activeClocks !== 0 ? 'active-clock-indicator' : undefined}
            style={{
              opacity: isDisplayingArrowButtons || isDisplayingCaptureButtons ? 0 : 1,
              position: 'relative',
              zIndex: 1,
            }}
            tooltip="Show Search / Task List"
          />

          {renderCaptureButtons()}
        </Fragment>
      }
    </div>
  );
};

const mapStateToProps = (state) => {
  const path = state.org.present.get('path');
  const files = state.org.present.get('files');
  const file = state.org.present.getIn(['files', path], Map());
  const fileSettings = state.org.present.get('fileSettings');
  const searchFiles = determineIncludedFiles(files, fileSettings, path, 'includeInSearch', false);
  const activeClocks = Object.values(
    searchFiles.map((f) => (f.get('headers').size ? f.get('activeClocks') : 0)).toJS()
  ).reduce((acc, val) => (typeof val === 'number' ? acc + val : acc), 0);
  return {
    selectedHeaderId: file.get('selectedHeaderId'),
    isDirty: file.get('isDirty'),
    isNarrowedHeaderActive: !!file.get('narrowedHeaderId'),
    selectedTableCellId: file.get('selectedTableCellId'),
    selectedListItemId: file.get('selectedListItemId'),
    captureTemplates: state.capture.get('captureTemplates', List()),
    path,
    isLoading: !state.base.get('isLoading').isEmpty(),
    online: state.base.get('online'),
    activeClocks,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    org: bindActionCreators(orgActions, dispatch),
    capture: bindActionCreators(captureActions, dispatch),
    base: bindActionCreators(baseActions, dispatch),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(ActionDrawer);