Skip to content
Snippets Groups Projects
MatchingContainerComponent.js 6.78 KiB
Newer Older
chris's avatar
chris committed
/* eslint-disable react/destructuring-assignment */
chris's avatar
chris committed
/* eslint-disable react/prop-types */
chris's avatar
chris committed
import React, { useContext, useEffect, useRef, useState } from 'react';
chris's avatar
chris committed
import { v4 as uuidv4 } from 'uuid';
chris's avatar
chris committed
import { WaxContext } from 'wax-prosemirror-core';
chris's avatar
chris committed
import { Icon } from 'wax-prosemirror-components';
chris's avatar
chris committed
import { DocumentHelpers } from 'wax-prosemirror-utilities';
chris's avatar
chris committed
import styled from 'styled-components';
import FeedbackComponent from './FeedbackComponent';
chris's avatar
chris committed
import ContainerEditor from './ContainerEditor';
chris's avatar
chris committed

const MatchingWrapper = styled.div`
chris's avatar
chris committed
  display: flex;
  flex-direction: column;
chris's avatar
chris committed
  margin: 0px 38px 15px 38px;
chris's avatar
chris committed
  margin-top: 10px;
`;
chris's avatar
chris committed

chris's avatar
chris committed
const MatchingContainer = styled.div`
  border: 3px solid #f5f5f7;
  margin-bottom: 30px;
  padding: 10px;
`;

chris's avatar
chris committed
const QuestionWrapper = styled.div`
  display: flex;
  flex-direction: row;
chris's avatar
chris committed
  width: 100%;
chris's avatar
chris committed
`;

chris's avatar
chris committed
const ActionButton = styled.button`
  background: transparent;
chris's avatar
chris committed
  cursor: pointer;
chris's avatar
chris committed
  padding-left: 0;
chris's avatar
chris committed
`;

const StyledIconAction = styled(Icon)`
  height: 24px;
  width: 24px;
chris's avatar
chris committed
`;

chris's avatar
chris committed
const CreateOptions = styled.div`
  display: flex;
  flex-direction: column;
chris's avatar
chris committed
  padding-bottom: 10px;
chris's avatar
chris committed
`;

chris's avatar
chris committed
const OptionArea = styled.div`
  display: flex;
chris's avatar
chris committed
  width: 100%;
chris's avatar
chris committed

  ul {
    display: flex;
    flex-direction: row;
chris's avatar
chris committed
    margin: 0;
    padding: 0;
chris's avatar
chris committed
    li {
      list-style-type: none;
      padding-bottom: 7px;
chris's avatar
chris committed

      span {
        background: #535e76;
chris's avatar
chris committed
        color: white;
        padding: 3px 3px 3px 10px;
      }
chris's avatar
chris committed
      svg {
        fill: white;
        height: 16px;
chris's avatar
chris committed
      }
    }
  }
chris's avatar
chris committed
`;

const AddOption = styled.div`
  display: flex;
chris's avatar
chris committed
  input {
    border: none;
    border-bottom: 1px solid black;
chris's avatar
chris committed
    &:focus {
      outline: none;
    }

    ::placeholder {
      color: rgb(170, 170, 170);
      font-style: italic;
    }
  }
chris's avatar
chris committed
  button {
chris's avatar
chris committed
    border: 1px solid #535e76;
    color: #535e76;
chris's avatar
chris committed
    margin-left: 20px;
    padding: 4px 8px 4px 8px;
chris's avatar
chris committed
    &:hover {
chris's avatar
chris committed
      border: 1px solid #535e76;
chris's avatar
chris committed
      cursor: pointer;
      margin-right: 20px;
      padding: 4px 8px 4px 8px;
    }
  }
chris's avatar
chris committed
`;

chris's avatar
chris committed
export default ({ node, view, getPos }) => {
  const context = useContext(WaxContext);
  const {
    pmViews: { main },
  } = context;
chris's avatar
chris committed

chris's avatar
chris committed
  const [options, setOptions] = useState(node.attrs.options);
chris's avatar
chris committed

chris's avatar
chris committed
  const [optionText, setOptionText] = useState('');
chris's avatar
chris committed
  const [addingOption, setAddingOption] = useState(false);
chris's avatar
chris committed
  const addOptionRef = useRef(null);
chris's avatar
chris committed

  const customProps = main.props.customValues;

  const isEditable = main.props.editable(editable => {
    return editable;
  });

  const readOnly = !isEditable;

chris's avatar
chris committed
  useEffect(() => {
    const allNodes = getNodes(main);
chris's avatar
chris committed

    /* TEMP TO SAVE NODE OPTIONS TODO: SAVE IN CONTEXT OPTIONS */
chris's avatar
chris committed
    saveInChildOptions(allNodes);

chris's avatar
chris committed
    if (!addingOption) return;
    allNodes.forEach(singleNode => {
      if (singleNode.node.attrs.id === node.attrs.id) {
        main.dispatch(
chris's avatar
chris committed
          main.state.tr
            .setMeta('addToHistory', false)
            .setNodeMarkup(getPos(), undefined, {
              ...singleNode.node.attrs,
              options,
            }),
chris's avatar
chris committed
        );
      }
    });
chris's avatar
chris committed
  }, [options, JSON.stringify(context.pmViews.main.state)]);
chris's avatar
chris committed

chris's avatar
chris committed
  const addOption = () => {
    if (addOptionRef.current.value.trim() === '') return;
chris's avatar
chris committed
    const obj = { label: addOptionRef.current.value, value: uuidv4() };
chris's avatar
chris committed
    setOptions(prevOptions => [...prevOptions, obj]);
    setAddingOption(true);
    setTimeout(() => {
      setAddingOption(false);
    });
    setOptionText('');
chris's avatar
chris committed
    addOptionRef.current.focus();
chris's avatar
chris committed
  };

  const updateOptionText = () => {
    setOptionText(addOptionRef.current.value);
  };

  const handleKeyDown = event => {
    if (event.key === 'Enter' || event.which === 13) {
      addOption();
    }
  };

chris's avatar
chris committed
  const removeOption = value => {
    setOptions(options.filter(option => option.value !== value));
chris's avatar
chris committed
    setAddingOption(true);
    setTimeout(() => {
      setAddingOption(false);
    });
chris's avatar
chris committed
  };

chris's avatar
chris committed
  const saveInChildOptions = allNodes => {
    allNodes.forEach(singleNode => {
      if (singleNode.node.attrs.id === node.attrs.id) {
        singleNode.node.content.content.forEach(parentNodes => {
          parentNodes.forEach(optionNode => {
            if (optionNode.type.name === 'matching_option')
              /* eslint-disable-next-line no-param-reassign */
chris's avatar
chris committed
              optionNode.attrs.options = options;
          });
        });
      }
    });
  };

chris's avatar
chris committed
  const { testMode } = customProps;

chris's avatar
chris committed
  return (
    <MatchingWrapper>
      <span>Matching</span>
      <MatchingContainer className="matching">
chris's avatar
chris committed
        <QuestionWrapper>
chris's avatar
chris committed
          <ContainerEditor getPos={getPos} node={node} view={view} />
chris's avatar
chris committed
        </QuestionWrapper>
        {(!readOnly ||
          (readOnly && !customProps.testMode && !customProps.showFeedBack)) && (
chris's avatar
chris committed
          <CreateOptions>
chris's avatar
chris committed
            <OptionArea>
chris's avatar
chris committed
              {options.length > 0 && (
                <ul>
                  <li>Options: </li>
                  {options.map((option, index) => {
                    return (
chris's avatar
chris committed
                      <li key={option.value}>
chris's avatar
chris committed
                        <span>
                          {option.label} &nbsp;
                          {!readOnly && (
                            <ActionButton
                              onClick={() => removeOption(option.value)}
                              type="button"
                            >
                              <StyledIconAction name="deleteOutlined" />
                            </ActionButton>
                          )}
chris's avatar
chris committed
                        </span>
                      </li>
                    );
                  })}
                </ul>
              )}
chris's avatar
chris committed
            </OptionArea>

            {!readOnly && (
              <AddOption>
                <input
                  onChange={updateOptionText}
                  onKeyPress={handleKeyDown}
                  placeholder="Type an option ..."
                  ref={addOptionRef}
                  type="text"
                  value={optionText}
                />
                <button onClick={addOption} type="button">
                  Add Option
                </button>
              </AddOption>
            )}
chris's avatar
chris committed
          </CreateOptions>
        )}
chris's avatar
chris committed
        {!testMode && (
chris's avatar
chris committed
          <FeedbackComponent
            getPos={getPos}
            node={node}
            readOnly={readOnly}
            view={view}
          />
        )}
      </MatchingContainer>
    </MatchingWrapper>
  );
};
chris's avatar
chris committed

const getNodes = view => {
  const allNodes = DocumentHelpers.findBlockNodes(view.state.doc);
  const matchingContainerNodes = [];
  allNodes.forEach(node => {
    if (node.node.type.name === 'matching_container') {
      matchingContainerNodes.push(node);
    }
  });
  return matchingContainerNodes;
};