From 81150eddb4f51314d9580970f58f679f8dea66ab Mon Sep 17 00:00:00 2001
From: chris <kokosias@yahoo.gr>
Date: Fri, 3 Dec 2021 18:17:00 +0200
Subject: [PATCH] true false single correct files

---
 .../MultipleChoiceSingleCorrectQuestion.js    |  40 -----
 .../TrueFalseSingleCorrectNodeView.js         |  56 +++++++
 .../TrueFalseSingleCorrectQuestion.js         |  65 +++++++
 .../components/QuestionComponent.js           | 158 ++++++++++++++++++
 .../components/SwitchComponent.js             | 117 +++++++++++++
 .../trueFalseSingleCorrectContainerNode.js    |  27 +++
 .../schema/trueFalseSingleCorrectNode.js      |  30 ++++
 7 files changed, 453 insertions(+), 40 deletions(-)
 create mode 100644 wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js
 create mode 100644 wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js
 create mode 100644 wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/QuestionComponent.js
 create mode 100644 wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/SwitchComponent.js
 create mode 100644 wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectContainerNode.js
 create mode 100644 wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectNode.js

diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js
index 20a294722..f143db4d6 100644
--- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js
@@ -3,50 +3,10 @@ import { isEmpty } from 'lodash';
 import { injectable } from 'inversify';
 import { Commands } from 'wax-prosemirror-utilities';
 import { v4 as uuidv4 } from 'uuid';
-import { Fragment } from 'prosemirror-model';
-import { TextSelection } from 'prosemirror-state';
-import { wrapIn } from 'prosemirror-commands';
 import ToolBarBtn from '../components/ToolBarBtn';
 import helpers from '../helpers/helpers';
 import Tools from '../../lib/Tools';
 
-const createOption = (main, context) => {
-  const { state, dispatch } = main;
-  /* Create Wrapping */
-  const { $from, $to } = state.selection;
-  const range = $from.blockRange($to);
-
-  wrapIn(state.config.schema.nodes.multiple_choice_single_correct_container, {
-    id: uuidv4(),
-  })(state, dispatch);
-
-  /* set New Selection */
-  dispatch(
-    main.state.tr.setSelection(
-      new TextSelection(main.state.tr.doc.resolve(range.$to.pos)),
-    ),
-  );
-
-  /* create First Option */
-  const firstOption = main.state.config.schema.nodes.multiple_choice_single_correct.create(
-    { id: uuidv4() },
-    Fragment.empty,
-  );
-  dispatch(main.state.tr.replaceSelectionWith(firstOption));
-
-  /* create Second Option */
-  const secondOption = main.state.config.schema.nodes.multiple_choice_single_correct.create(
-    { id: uuidv4() },
-    Fragment.empty,
-  );
-  dispatch(main.state.tr.replaceSelectionWith(secondOption));
-
-  setTimeout(() => {
-    helpers.createEmptyParagraph(context, secondOption.attrs.id);
-    helpers.createEmptyParagraph(context, firstOption.attrs.id);
-  }, 50);
-};
-
 @injectable()
 class MultipleChoiceSingleCorrectQuestion extends Tools {
   title = 'Add Multiple Choice Single Correct Question';
diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js
new file mode 100644
index 000000000..327937384
--- /dev/null
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js
@@ -0,0 +1,56 @@
+import AbstractNodeView from '../../PortalService/AbstractNodeView';
+
+export default class TrueFalseSingleCorrectNodeView extends AbstractNodeView {
+  constructor(
+    node,
+    view,
+    getPos,
+    decorations,
+    createPortal,
+    Component,
+    context,
+  ) {
+    super(node, view, getPos, decorations, createPortal, Component, context);
+
+    this.node = node;
+    this.outerView = view;
+    this.getPos = getPos;
+    this.context = context;
+  }
+
+  static name() {
+    return 'true_false_single_correct';
+  }
+
+  update(node) {
+    // if (!node.sameMarkup(this.node)) return false;
+    this.node = node;
+    if (this.context.view[node.attrs.id]) {
+      const { state } = this.context.view[node.attrs.id];
+      const start = node.content.findDiffStart(state.doc.content);
+      if (start != null) {
+        let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
+        const overlap = start - Math.min(endA, endB);
+        if (overlap > 0) {
+          endA += overlap;
+          endB += overlap;
+        }
+        this.context.view[node.attrs.id].dispatch(
+          state.tr
+            .replace(start, endB, node.slice(start, endA))
+            .setMeta('fromOutside', true),
+        );
+      }
+    }
+
+    return true;
+  }
+
+  stopEvent(event) {
+    if (event.target.type === 'text') {
+      return true;
+    }
+    const innerView = this.context.view[this.node.attrs.id];
+    return innerView && innerView.dom.contains(event.target);
+  }
+}
diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js
new file mode 100644
index 000000000..8014abaa0
--- /dev/null
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js
@@ -0,0 +1,65 @@
+import React from 'react';
+import { isEmpty } from 'lodash';
+import { injectable } from 'inversify';
+import { Commands } from 'wax-prosemirror-utilities';
+import { v4 as uuidv4 } from 'uuid';
+import ToolBarBtn from '../components/ToolBarBtn';
+import helpers from '../helpers/helpers';
+import Tools from '../../lib/Tools';
+
+@injectable()
+class MultipleChoiceSingleCorrectQuestion extends Tools {
+  title = 'Add True False Single Correct Question';
+  icon = 'multipleChoice';
+  name = 'True False Single Correct';
+  label = 'True False Single Correct';
+
+  get run() {
+    return (view, context) => {
+      helpers.createOptions(
+        view,
+        context,
+        view.state.config.schema.nodes.true_false_single_correct,
+      );
+    };
+  }
+
+  get active() {
+    return state => {
+      return Commands.isParentOfType(
+        state,
+        state.config.schema.nodes.true_false_single_correct,
+      );
+    };
+  }
+
+  select = (state, activeView) => {
+    const { disallowedTools } = activeView.props;
+    if (disallowedTools.includes('MultipleChoice')) return false;
+    let status = true;
+    const { from, to } = state.selection;
+
+    if (from === null) return false;
+
+    state.doc.nodesBetween(from, to, (node, pos) => {
+      if (node.type.groups.includes('questions')) {
+        status = false;
+      }
+    });
+    return status;
+  };
+
+  get enable() {
+    return state => {};
+  }
+
+  renderTool(view) {
+    if (isEmpty(view)) return null;
+    // eslint-disable-next-line no-underscore-dangle
+    return this._isDisplayed ? (
+      <ToolBarBtn item={this.toJSON()} key={uuidv4()} view={view} />
+    ) : null;
+  }
+}
+
+export default MultipleChoiceSingleCorrectQuestion;
diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/QuestionComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/QuestionComponent.js
new file mode 100644
index 000000000..1fb3cca3d
--- /dev/null
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/QuestionComponent.js
@@ -0,0 +1,158 @@
+/* eslint-disable react/prop-types */
+import React, { useContext } from 'react';
+import styled from 'styled-components';
+import { TextSelection } from 'prosemirror-state';
+import { WaxContext } from 'wax-prosemirror-core';
+import { PlusSquareOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Fragment } from 'prosemirror-model';
+import { v4 as uuidv4 } from 'uuid';
+import helpers from '../../helpers/helpers';
+import EditorComponent from '../../components/EditorComponent';
+import FeedbackComponent from '../../components/FeedbackComponent';
+import Button from '../../components/Button';
+import SwitchComponent from './SwitchComponent';
+
+const Wrapper = styled.div`
+  display: flex;
+  flex-direction: row;
+  width: 100%;
+`;
+
+const InfoRow = styled.div`
+  color: black;
+  display: flex;
+  flex-direction: row;
+  padding: 10px 0px 4px 0px;
+`;
+
+const QuestionNunber = styled.span`
+  &:before {
+    content: 'Answer ' counter(question-item-multiple);
+    counter-increment: question-item-multiple;
+  }
+`;
+
+const QuestionControlsWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+`;
+
+const QuestionWrapper = styled.div`
+  border: 1px solid #a5a1a2;
+  border-radius: 4px;
+  color: black;
+  display: flex;
+  flex: 2 1 auto;
+  flex-direction: column;
+  padding: 10px;
+`;
+
+const IconsWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+
+  button {
+    border: none;
+    box-shadow: none;
+  }
+
+  span {
+    cursor: pointer;
+  }
+`;
+
+const QuestionData = styled.div`
+  align-items: normal;
+  display: flex;
+  flex-direction: row;
+`;
+
+export default ({ node, view, getPos }) => {
+  const context = useContext(WaxContext);
+  const {
+    view: { main },
+  } = context;
+
+  const isEditable = main.props.editable(editable => {
+    return editable;
+  });
+
+  const removeOption = () => {
+    main.state.doc.nodesBetween(getPos(), getPos() + 1, (sinlgeNode, pos) => {
+      if (sinlgeNode.attrs.id === node.attrs.id) {
+        main.dispatch(
+          main.state.tr.deleteRange(getPos(), getPos() + sinlgeNode.nodeSize),
+        );
+      }
+    });
+  };
+
+  const addOption = nodeId => {
+    const newAnswerId = uuidv4();
+    context.view.main.state.doc.descendants((editorNode, index) => {
+      if (editorNode.type.name === 'multiple_choice_single_correct') {
+        if (editorNode.attrs.id === nodeId) {
+          context.view.main.dispatch(
+            context.view.main.state.tr.setSelection(
+              new TextSelection(
+                context.view.main.state.tr.doc.resolve(
+                  editorNode.nodeSize + index,
+                ),
+              ),
+            ),
+          );
+
+          const answerOption = context.view.main.state.config.schema.nodes.multiple_choice_single_correct.create(
+            { id: newAnswerId },
+            Fragment.empty,
+          );
+          context.view.main.dispatch(
+            context.view.main.state.tr.replaceSelectionWith(answerOption),
+          );
+          // create Empty Paragraph
+          setTimeout(() => {
+            helpers.createEmptyParagraph(context, newAnswerId);
+          }, 120);
+        }
+      }
+    });
+  };
+
+  const readOnly = !isEditable;
+  const showAddIcon = true;
+  const showRemoveIcon = true;
+
+  return (
+    <Wrapper>
+      <QuestionControlsWrapper>
+        <InfoRow>
+          <QuestionNunber />
+          <SwitchComponent getPos={getPos} node={node} />
+        </InfoRow>
+        <QuestionWrapper>
+          <QuestionData>
+            <EditorComponent getPos={getPos} node={node} view={view} />
+          </QuestionData>
+          <FeedbackComponent getPos={getPos} node={node} view={view} />
+        </QuestionWrapper>
+      </QuestionControlsWrapper>
+      <IconsWrapper>
+        {showAddIcon && !readOnly && (
+          <Button
+            icon={<PlusSquareOutlined title="Add Option" />}
+            onClick={() => addOption(node.attrs.id)}
+          />
+        )}
+        {showRemoveIcon && !readOnly && (
+          <Button
+            icon={
+              <DeleteOutlined onClick={removeOption} title="Delete Option" />
+            }
+          />
+        )}
+      </IconsWrapper>
+    </Wrapper>
+  );
+};
diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/SwitchComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/SwitchComponent.js
new file mode 100644
index 000000000..e9110f8f3
--- /dev/null
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/SwitchComponent.js
@@ -0,0 +1,117 @@
+/* eslint-disable react/prop-types */
+
+import React, { useState, useContext, useEffect } from 'react';
+import { WaxContext } from 'wax-prosemirror-core';
+import { DocumentHelpers } from 'wax-prosemirror-utilities';
+import { NodeSelection } from 'prosemirror-state';
+import styled from 'styled-components';
+import Switch from '../../components/Switch';
+
+const StyledSwitch = styled(Switch)`
+  display: flex;
+  margin-left: auto;
+
+  .ant-switch-checked {
+    background-color: green;
+  }
+`;
+
+const CustomSwitch = ({ node, getPos }) => {
+  const context = useContext(WaxContext);
+  const [checked, setChecked] = useState(false);
+  const {
+    view: { main },
+  } = context;
+
+  useEffect(() => {
+    const allNodes = getNodes(main);
+    allNodes.forEach(singNode => {
+      if (singNode.node.attrs.id === node.attrs.id) {
+        setChecked(singNode.node.attrs.correct);
+      }
+    });
+  }, [getNodes(main)]);
+
+  const handleChange = () => {
+    setChecked(!checked);
+    main.dispatch(
+      main.state.tr.setSelection(
+        NodeSelection.create(main.state.doc, getPos()),
+      ),
+    );
+    const parentContainer = findParentOfType(
+      main.state,
+      main.state.config.schema.nodes.multiple_choice_single_correct_container,
+    );
+    let parentPosition = 0;
+
+    main.state.doc.descendants((parentNode, parentPos) => {
+      if (
+        parentNode.type.name === 'multiple_choice_single_correct_container' &&
+        parentNode.attrs.id === parentContainer.attrs.id
+      ) {
+        parentPosition = parentPos;
+      }
+    });
+
+    const { tr } = main.state;
+
+    parentContainer.descendants((element, position) => {
+      if (
+        element.type.name === 'multiple_choice_single_correct' &&
+        element.attrs.id === node.attrs.id
+      ) {
+        tr.setNodeMarkup(getPos(), undefined, {
+          ...element.attrs,
+          correct: !checked,
+        });
+      } else if (
+        element.type.name === 'multiple_choice_single_correct' &&
+        element.attrs.correct
+      ) {
+        tr.setNodeMarkup(parentPosition + position + 1, undefined, {
+          ...element.attrs,
+          correct: false,
+        });
+      }
+    });
+
+    main.dispatch(tr);
+  };
+
+  return (
+    <StyledSwitch
+      checked={checked}
+      checkedChildren="YES"
+      label="Correct?"
+      labelPosition="left"
+      onChange={handleChange}
+      unCheckedChildren="NO"
+    />
+  );
+};
+
+const getNodes = view => {
+  const allNodes = DocumentHelpers.findBlockNodes(view.state.doc);
+  const multipleChoiceNodes = [];
+  allNodes.forEach(node => {
+    if (node.node.type.name === 'multiple_choice_single_correct') {
+      multipleChoiceNodes.push(node);
+    }
+  });
+  return multipleChoiceNodes;
+};
+
+export default CustomSwitch;
+
+const findParentOfType = (state, nodeType) => {
+  let nodeFound = '';
+  const predicate = node => node.type === nodeType;
+  for (let i = state.selection.$from.depth; i > 0; i -= 1) {
+    const node = state.selection.$from.node(i);
+    if (predicate(node)) {
+      nodeFound = node;
+    }
+  }
+  return nodeFound;
+};
diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectContainerNode.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectContainerNode.js
new file mode 100644
index 000000000..fa5c5fd1a
--- /dev/null
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectContainerNode.js
@@ -0,0 +1,27 @@
+const trueFalseSingleCorrectContainerNode = {
+  attrs: {
+    id: { default: '' },
+    class: { default: 'true-false-single-correct' },
+  },
+  group: 'block questions',
+  atom: true,
+  selectable: true,
+  draggable: true,
+  content: 'block+',
+  parseDOM: [
+    {
+      tag: 'div.true-false-single-correct',
+      getAttrs(dom) {
+        return {
+          id: dom.dataset.id,
+          class: dom.getAttribute('class'),
+        };
+      },
+    },
+  ],
+  toDOM(node) {
+    return ['div', node.attrs, 0];
+  },
+};
+
+export default trueFalseSingleCorrectContainerNode;
diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectNode.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectNode.js
new file mode 100644
index 000000000..b07201427
--- /dev/null
+++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/schema/trueFalseSingleCorrectNode.js
@@ -0,0 +1,30 @@
+import { v4 as uuidv4 } from 'uuid';
+
+const trueFalseSingleCorrectNode = {
+  attrs: {
+    class: { default: 'true-false-single-correct-option' },
+    id: { default: uuidv4() },
+    correct: { default: false },
+    feedback: { default: '' },
+  },
+  group: 'block questions',
+  content: 'block*',
+  defining: true,
+
+  parseDOM: [
+    {
+      tag: 'true-false-single-correct-option',
+      getAttrs(dom) {
+        return {
+          id: dom.getAttribute('id'),
+          class: dom.getAttribute('class'),
+          correct: JSON.parse(dom.getAttribute('correct').toLowerCase()),
+          feedback: dom.getAttribute('feedback'),
+        };
+      },
+    },
+  ],
+  toDOM: node => ['div', node.attrs, 0],
+};
+
+export default trueFalseSingleCorrectNode;
-- 
GitLab