diff --git a/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js b/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js index b23b840f1d022004f752bbf963784565e3a8b132..cb84528fa5f96a8f2e32d3d47d5765940829ea60 100644 --- a/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js +++ b/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js @@ -1,4 +1,6 @@ import { injectable } from 'inversify'; +import { wrapIn } from 'prosemirror-commands'; +import { v4 as uuidv4 } from 'uuid'; import Tools from '../lib/Tools'; @injectable() @@ -8,10 +10,26 @@ class MatchingQuestion extends Tools { name = 'Matching'; get run() { - return (state, dispatch) => {}; + return (state, dispatch) => { + wrapIn(state.config.schema.nodes.matching_container, { + id: uuidv4(), + })(state, dispatch); + }; } - select = (state, activeViewId) => {}; + select = (state, activeViewId, activeView) => { + const { disallowedTools } = activeView.props; + let status = true; + const { from, to } = state.selection; + if (from === null || disallowedTools.includes('Matching')) return false; + + state.doc.nodesBetween(from, to, (node, pos) => { + if (node.type.groups.includes('questions')) { + status = false; + } + }); + return status; + }; get active() { return state => {}; diff --git a/wax-prosemirror-services/src/MatchingService/MatchingService.js b/wax-prosemirror-services/src/MatchingService/MatchingService.js index 83f1f0395ac10e6042a6c108f575e9d12d03f0e0..c95696a11b9f41ef9c8726ba96fe461f7fbe91b5 100644 --- a/wax-prosemirror-services/src/MatchingService/MatchingService.js +++ b/wax-prosemirror-services/src/MatchingService/MatchingService.js @@ -2,6 +2,7 @@ import Service from '../Service'; import MatchingQuestion from './MatchingQuestion'; import matchingContainerNode from './schema/matchingContainerNode'; import MatchingContainerNodeView from './MatchingContainerNodeView'; +import MatchingContainerComponent from './components/MatchingContainerComponent'; class MatchingService extends Service { name = 'MatchingService'; @@ -15,11 +16,11 @@ class MatchingService extends Service { matching_container: matchingContainerNode, }); - // addPortal({ - // nodeView: MatchingContainerNodeView, - // component: QuestionComponent, - // context: this.app, - // }); + addPortal({ + nodeView: MatchingContainerNodeView, + component: MatchingContainerComponent, + context: this.app, + }); } } diff --git a/wax-prosemirror-services/src/MatchingService/components/FeedbackComponent.js b/wax-prosemirror-services/src/MatchingService/components/FeedbackComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..0afd56b88d70dcfe21cf7faabbe0af016fef4375 --- /dev/null +++ b/wax-prosemirror-services/src/MatchingService/components/FeedbackComponent.js @@ -0,0 +1,111 @@ +/* eslint-disable react/prop-types */ + +import React, { useContext, useRef, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { TextSelection } from 'prosemirror-state'; +import { WaxContext } from 'wax-prosemirror-core'; +import { DocumentHelpers } from 'wax-prosemirror-utilities'; + +const FeedBack = styled.div` + color: black; + margin-top: 10px; + padding: 10px; +`; + +const FeedBackLabel = styled.span` + font-weight: 700; +`; + +const FeedBackInput = styled.input` + border: none; + border-bottom: 1px solid black; + display: flex; + width: 100%; + + &:focus { + outline: none; + } + + ::placeholder { + color: rgb(170, 170, 170); + font-style: italic; + } +`; + +export default ({ node, view, getPos, readOnly }) => { + const context = useContext(WaxContext); + const { + pmViews: { main }, + } = context; + const [feedBack, setFeedBack] = useState(' '); + const [isFirstRun, setFirstRun] = useState(true); + const [typing, setTyping] = useState(false); + const feedBackRef = useRef(null); + + useEffect(() => { + const allNodes = getNodes(main); + allNodes.forEach(singleNode => { + if (singleNode.node.attrs.id === node.attrs.id) { + if (!typing || context.transaction.meta.inputType === 'Redo') { + setFeedBack(singleNode.node.attrs.feedback); + } + if (!isFirstRun) { + if (singleNode.node.attrs.feedback === '') + setFeedBack(singleNode.node.attrs.feedback); + } + } + }); + }, [getNodes(main)]); + + const handleKeyDown = e => { + setTyping(true); + if (e.key === 'Backspace') { + main.dispatch( + main.state.tr.setSelection( + TextSelection.create(main.state.tr.doc, null), + ), + ); + } + }; + + const feedBackInput = () => { + setFeedBack(feedBackRef.current.value); + }; + + const saveFeedBack = () => { + return false; + }; + + const onFocus = () => { + main.dispatch( + main.state.tr.setSelection(TextSelection.create(main.state.tr.doc, null)), + ); + }; + + return ( + <FeedBack> + <FeedBackLabel>Feedback</FeedBackLabel> + <FeedBackInput + disabled={readOnly} + onBlur={saveFeedBack} + onChange={feedBackInput} + onFocus={onFocus} + onKeyDown={handleKeyDown} + placeholder="Insert feedback" + ref={feedBackRef} + type="text" + value={feedBack} + /> + </FeedBack> + ); +}; + +const getNodes = view => { + const allNodes = DocumentHelpers.findBlockNodes(view.state.doc); + const fillTheGapNodes = []; + allNodes.forEach(node => { + if (node.node.type.name === 'fill_the_gap_container') + fillTheGapNodes.push(node); + }); + return fillTheGapNodes; +}; diff --git a/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js b/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..2099fe9a652ec189c378f5649db4b6a2f0293827 --- /dev/null +++ b/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js @@ -0,0 +1,48 @@ +/* eslint-disable react/prop-types */ +import React, { useContext } from 'react'; +import { WaxContext } from 'wax-prosemirror-core'; +import styled from 'styled-components'; +import FeedbackComponent from './FeedbackComponent'; + +const MatchingContainer = styled.div` + border: 3px solid #f5f5f7; + margin-bottom: 30px; +`; + +const MatchingWrapper = styled.div` + margin-bottom: ; + margin: 0px 38px 15px 38px; + + margin-top: 10px; +`; + +export default ({ node, view, getPos }) => { + const context = useContext(WaxContext); + const { + pmViews: { main }, + } = context; + + const customProps = main.props.customValues; + + const isEditable = main.props.editable(editable => { + return editable; + }); + + const readOnly = !isEditable; + + return ( + <MatchingWrapper> + <span>Matching</span> + <MatchingContainer className="matching"> + {!(readOnly && !customProps.showFeedBack) && ( + <FeedbackComponent + getPos={getPos} + node={node} + readOnly={readOnly} + view={view} + /> + )} + </MatchingContainer> + </MatchingWrapper> + ); +};