2 lines
15 KiB
JavaScript
2 lines
15 KiB
JavaScript
"use strict";(self.webpackChunkWebComponents=self.webpackChunkWebComponents||[]).push([[566],{14566:(t,e,n)=>{n.r(e),n.d(e,{MatchingProblem:()=>c});var o=n(78673),s=n(8152);class i extends s.s{correctAnswers(){return Array.from(this.doc.querySelectorAll("edge")).map((t=>Array.from(t.querySelectorAll("label")).slice(0,2).map((t=>t.textContent.trim()))))}}class c extends o.A{constructor(t){super(t);let e=t.orig;this.containerDiv=t.orig;const n=e.querySelector("script");if(n){let t;try{if("text/xml"==n.type){const e=n.textContent;t=new i(e).toJson()}else t=JSON.parse(n.textContent);this.boxData=t}catch(t){console.error("Failed to parse boxData JSON:",t)}}this.divid=e.id,this.boxesRenderedPromise=new Promise((t=>{this.boxesRenderedResolve=t})),this.workspace=this.createWorkspace(e);try{this.statement=this.createStatement(e)}catch(t){console.error("Error setting statement:",t)}this.connList=this.createConnList(e),this.ariaLive=this.createAriaLive(e),this.controlDiv=this.createControlDiv(e),this.createHelpModal(),this.connections=[],this.allBoxes=[],this.selectedBox=null,this.startBox=null,this.tempLine=null,this.useRunestoneServices=eBookConfig.useRunestoneServices,this.graderactive=t.graderactive||!1,this.init(),this.boxesRenderedPromise.then((()=>{this.checkServer("matching",!0)}))}init(){this.shuffle(this.boxData.left),this.shuffle(this.boxData.right),this.renderBoxes(),this.attachEvents(),this.queueMathJax(this.containerDiv)}checkCurrentAnswer(){const t=this.boxData.correctAnswers,e=this.connections.map((t=>[t.fromBox.dataset.id,t.toBox.dataset.id])),n=t.filter((t=>e.some((e=>e[0]===t[0]&&e[1]===t[1])))),o=e.filter((e=>!t.some((t=>t[0]===e[0]&&t[1]===e[1]))));this.correctCount=n.length,this.incorrectCount=o.length,this.missingCount=t.length-this.correctCount,this.denominator=this.correctCount+this.incorrectCount+this.missingCount,this.scorePercent=0===this.denominator?0:Math.max(0,Math.min(100,Math.round(this.correctCount/this.denominator*100)))}async logCurrentAnswer(){let t={score:this.scorePercent,percent:this.scorePercent/100,correctCount:this.correctCount,incorrectCount:this.incorrectCount,missingCount:this.missingCount,connections:this.connections.map((t=>({from:t.fromBox.dataset.id,to:t.toBox.dataset.id}))),event:"matching"};t.div_id=this.divid,t.act=`score:${t.score} connections:${JSON.stringify(t.connections)}`,t.correct=100===t.score,t.answer=JSON.stringify({connections:t.connections}),await this.logBookEvent(t)}renderFeedback(){this.allBoxes.forEach((t=>t.classList.remove("match-correct","match-incorrect"))),this.connections.forEach((t=>{const e=[t.fromBox.dataset.id,t.toBox.dataset.id],n=this.boxData.correctAnswers.some((t=>t[0]===e[0]&&t[1]===e[1]));t.line.classList.remove("correct","incorrect"),t.line.classList.add(n?"correct":"incorrect"),[t.fromBox,t.toBox].forEach((t=>{n?t.classList.contains("match-incorrect")||t.classList.add("match-correct"):(t.classList.remove("match-correct"),t.classList.add("match-incorrect"))}))}));const t=100===this.scorePercent?" match-score-perfect":"";this.connList.innerHTML=`<div class="match-results"><span class="match-score-badge${t}">Score: ${this.scorePercent}%</span><span class="match-counts">${this.correctCount} correct · ${this.incorrectCount} incorrect · ${this.missingCount} missing</span></div>`,100!==this.scorePercent&&this.boxData.feedback&&this.boxData.feedback.trim()&&(this.connList.innerHTML+=`<div class="match_feedback exercise-content"><strong>Feedback:</strong> ${this.boxData.feedback}</div>`),this.queueMathJax(this.connList)}createStatement(t){const e=document.createElement("div");return e.className="statement",e.classList.add("match_question"),e.classList.add("exercise-statement"),e.innerHTML=this.boxData.statement,t.insertBefore(e,t.firstChild),e}restoreAnswers(t){t&&(this.connections=t.answer.connections.map((t=>({fromBox:this.allBoxes.find((e=>e.dataset.id===t.from)),toBox:this.allBoxes.find((e=>e.dataset.id===t.to))}))),this.updateConnectionModel(),this.correct=t.correct),this.connections.forEach((t=>{const e=this.getRightBoxCenter(t.fromBox),n=this.getLeftBoxCenter(t.toBox),o=this.createLineElement(e,n);o.fromBox=t.fromBox,o.toBox=t.toBox,this.svg.appendChild(o),t.line=o}))}checkLocalStorage(){if(this.graderactive)return;const t=localStorage.getItem(this.localStorageKey());if(t){const e=JSON.parse(t);if(e.timestamp&&e.timestamp<eBookConfig.termStartDate)return void localStorage.removeItem(this.localStorageKey());this.connections=e.connections.map((t=>({fromBox:this.allBoxes.find((e=>e.dataset.id===t.from)),toBox:this.allBoxes.find((e=>e.dataset.id===t.to))}))),this.updateConnectionModel(),this.correctCount=e.correctCount,this.incorrectCount=e.incorrectCount,this.missingCount=e.missingCount,this.scorePercent=e.score,this.restoreAnswers(),this.renderFeedback()}}setLocalStorage(){const t=new Date,e={connections:this.connections.map((t=>({from:t.fromBox.dataset.id,to:t.toBox.dataset.id}))),score:this.scorePercent,correctCount:this.correctCount,incorrectCount:this.incorrectCount,missingCount:this.missingCount,timestamp:t};localStorage.setItem(this.localStorageKey(),JSON.stringify(e))}disableInteraction(){}createWorkspace(t){const e=document.createElement("div");e.className="matching-workspace";const n=document.createElement("div");n.className="left-column",this.leftColumn=n;const o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.classList.add("connector-svg"),this.svg=o;const s=document.createElement("div");return s.className="right-column",this.rightColumn=s,e.appendChild(n),e.appendChild(o),e.appendChild(s),t.insertBefore(e,t.firstChild),e}createConnList(t){const e=document.createElement("div");return e.className="conn-list",e.innerHTML='<strong>Connections:</strong><div class="conn-entry conn-empty">No connections yet. Drag between boxes to connect them.</div>',t.appendChild(e),e}createAriaLive(t){const e=document.createElement("div");return e.className="aria-live",e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","true"),t.appendChild(e),e}createControlDiv(t){const e=document.createElement("div");e.className="control-div";const n=document.createElement("button");n.className="grade-button",n.textContent="Check Me",n.classList.add("btn","btn-success");const o=document.createElement("button");o.className="reset-button",o.textContent="Reset",o.classList.add("btn","btn-default");const s=document.createElement("button");return s.className="help-button",s.textContent="?",s.setAttribute("aria-label","Help"),e.appendChild(n),e.appendChild(o),e.appendChild(s),t.appendChild(e),n.addEventListener("click",(()=>this.gradeConnections())),o.addEventListener("click",(()=>this.resetConnections())),s.addEventListener("click",(()=>this.showHelp())),this.gradeBtn=n,this.resetBtn=o,this.helpBtn=s,e}createHelpModal(){this.helpModal=document.createElement("div"),this.helpModal.className="help-modal",this.helpModal.innerHTML='\n <div class="help-modal-content">\n <button class="help-close">×</button>\n <div class="help-text"><p>Click and drag between boxes to create connections.</p>\n <p>Use the tab key to navigate to a box and press Enter to select it. Focus then jumps to the other column; tab to the box you want to connect and press Enter. Press Escape to cancel a selection.</p>\n <p>Click on a connection line to remove it. You can also use the tab key to select lines. Press the delete key to remove a selected line.</p>\n <p>Click the "Check Me" button to check your connections, and save your work.</p>\n <p>Click the "Reset" button to clear all connections.</p></div>\n </div>',this.containerDiv.appendChild(this.helpModal),this.helpModal.querySelector(".help-close").addEventListener("click",(()=>this.hideHelp()))}showHelp(){this.helpModal.style.display="flex"}hideHelp(){this.helpModal.style.display="none"}shuffle(t){for(let e=t.length-1;e>0;e--){const n=Math.floor(Math.random()*(e+1));[t[e],t[n]]=[t[n],t[e]]}return t}renderBoxes(){this.boxData.left.forEach((({id:t,label:e})=>{const n=this.createBox(t,e,"drag");this.leftColumn.appendChild(n),this.allBoxes.push(n)})),this.boxData.right.forEach((({id:t,label:e})=>{const n=this.createBox(t,e,"drop");this.rightColumn.appendChild(n),this.allBoxes.push(n)}));const t=Array.from(this.workspace.querySelectorAll("img"));0===t.length&&this.boxesRenderedResolve();const e=t.map((t=>"function"==typeof t.decode?t.decode():t.complete&&0!==t.naturalWidth?Promise.resolve():new Promise((e=>{t.addEventListener("load",(()=>e())),t.addEventListener("error",(()=>e()))}))));Promise.all(e).then((()=>{this.boxesRenderedResolve()}))}createBox(t,e,n){const o=document.createElement("div");return o.className="box",o.dataset.id=t,o.dataset.role=n,o.innerHTML=e,o.tabIndex=0,o.setAttribute("role","button"),o.setAttribute("aria-label",`${"drag"===n?"Draggable":"Droppable"}: ${e}`),o}getCenter(t){const e=t.getBoundingClientRect(),n=this.workspace.getBoundingClientRect();return{x:e.left-n.left+e.width/2,y:e.top-n.top+e.height/2}}getRightBoxCenter(t){const e=t.getBoundingClientRect(),n=this.workspace.getBoundingClientRect();return{x:e.left-n.left+e.width,y:e.top-n.top+e.height/2}}getLeftBoxCenter(t){const e=t.getBoundingClientRect(),n=this.workspace.getBoundingClientRect();return{x:e.left-n.left,y:e.top-n.top+e.height/2}}getPortCenter(t){return"drop"===t.dataset.role?this.getLeftBoxCenter(t):this.getRightBoxCenter(t)}setLineEndpoints(t,e,n){const o=Math.max(30,Math.abs(n.x-e.x)/2),s=n.x>=e.x?1:-1;t.setAttribute("d",`M ${e.x} ${e.y} C ${e.x+s*o} ${e.y}, ${n.x-s*o} ${n.y}, ${n.x} ${n.y}`)}createLineElement(t,e){const n=document.createElementNS("http://www.w3.org/2000/svg","path");return this.setLineEndpoints(n,t,e),n.setAttribute("class","line"),n.setAttribute("tabindex","0"),n.setAttribute("focusable","true"),n.setAttribute("role","button"),n.setAttribute("aria-label","Connection line. Press Delete to remove."),n.addEventListener("click",(()=>{this.removeLine(n)})),n.addEventListener("keydown",(t=>{"Delete"!==t.key&&"Backspace"!==t.key||(t.preventDefault(),this.removeLine(n))})),n}removeLine(t){this.svg.removeChild(t);const e=this.connections.findIndex((e=>e.fromBox===t.fromBox&&e.toBox===t.toBox||e.fromBox===t.toBox&&e.toBox===t.fromBox));-1!==e&&this.connections.splice(e,1),this.updateConnectionModel()}isConnected(t,e){return this.connections.some((n=>n.fromBox===t&&n.toBox===e||n.fromBox===e&&n.toBox===t))}createPermanentLine(t,e){if(t.dataset.role===e.dataset.role)return this.flashInvalid(t,e),this.ariaLive&&(this.ariaLive.textContent="Connections must go between the left column and the right column."),!1;if("drop"===t.dataset.role&&([t,e]=[e,t]),this.isConnected(t,e))return!1;const n=this.getRightBoxCenter(t),o=this.getLeftBoxCenter(e),s=this.createLineElement(n,o);return s.fromBox=t,s.toBox=e,this.svg.appendChild(s),this.connections.push({fromBox:t,toBox:e,line:s}),this.updateConnectionModel(),this.isAnswered=!0,this.ariaLive&&(this.ariaLive.textContent=`Connected ${t.textContent} to ${e.textContent}`),!0}flashInvalid(...t){t.forEach((t=>{t.classList.add("invalid"),setTimeout((()=>t.classList.remove("invalid")),500)}))}updateConnectionModel(){if(this.allBoxes.forEach((t=>t.classList.remove("match-correct","match-incorrect"))),this.connList.innerHTML="<strong>Connections:</strong>",0===this.connections.length){const t=document.createElement("div");return t.className="conn-entry conn-empty",t.textContent="No connections yet. Drag between boxes to connect them.",void this.connList.appendChild(t)}this.connections.forEach((t=>{t.line&&t.line.classList.remove("correct","incorrect");const e=t.fromBox.textContent;let n=t.toBox.textContent;n||(n=t.toBox.querySelector("img").alt);const o=document.createElement("div");o.className="conn-entry",o.textContent=`${e} → ${n}`,this.connList.appendChild(o)}))}gradeConnections(){this.checkCurrentAnswer(),this.renderFeedback(),this.logCurrentAnswer(),this.setLocalStorage()}resetConnections(){this.connections.forEach((t=>{t.line&&t.line.parentNode===this.svg&&this.svg.removeChild(t.line)})),this.connections.length=0,this.updateConnectionModel(),this.ariaLive&&(this.ariaLive.textContent="All connections have been cleared."),this.logBookEvent({event:"matching_reset",div_id:this.divid,act:"reset all connections"})}attachEvents(){this.allBoxes.forEach((t=>{t.addEventListener("pointerdown",(e=>{e.preventDefault(),this.startBox=t;const n=this.getPortCenter(this.startBox);this.tempLine=this.createLineElement(n,n),this.tempLine.classList.add("temp"),this.svg.appendChild(this.tempLine),document.addEventListener("pointermove",this.updateTempLine),document.addEventListener("pointerup",this.finishConnection)})),t.addEventListener("keydown",(e=>{if("Enter"===e.key)if(e.preventDefault(),this.selectedBox){t!==this.selectedBox&&this.createPermanentLine(this.selectedBox,t),this.selectedBox.classList.remove("selected"),this.selectedBox=null;const e=this.allBoxes.indexOf(t),n=this.allBoxes[e+1];n?n.focus():this.allBoxes[0].focus()}else{this.selectedBox=t,t.classList.add("selected");const e=this.allBoxes.find((e=>e.dataset.role!==t.dataset.role));e&&e.focus(),this.ariaLive&&(this.ariaLive.textContent=`Selected ${t.textContent}. Tab to a box in the other column and press Enter to connect, or press Escape to cancel.`)}else if("Escape"===e.key&&this.selectedBox){e.preventDefault();const t=this.selectedBox;t.classList.remove("selected"),this.selectedBox=null,t.focus(),this.ariaLive&&(this.ariaLive.textContent="Selection cancelled.")}})),t.addEventListener("mouseenter",(()=>{this.connections.forEach((e=>{e.fromBox===t||e.toBox===t?(e.line.classList.add("highlighted"),e.line.classList.remove("faded")):(e.line.classList.add("faded"),e.line.classList.remove("highlighted"))}))})),t.addEventListener("mouseleave",(()=>{this.connections.forEach((t=>{t.line.classList.remove("highlighted","faded")}))}))})),window.addEventListener("resize",(()=>{this.connections.forEach((t=>{const e=this.getRightBoxCenter(t.fromBox),n=this.getLeftBoxCenter(t.toBox);this.setLineEndpoints(t.line,e,n)}))}))}updateTempLine=t=>{if(t.preventDefault(),!this.startBox||!this.tempLine)return;const e=this.getPortCenter(this.startBox),n=this.workspace.getBoundingClientRect(),o={x:t.clientX-n.left,y:t.clientY-n.top};this.setLineEndpoints(this.tempLine,e,o)};finishConnection=t=>{t.preventDefault(),this.tempLine&&(this.svg.removeChild(this.tempLine),this.tempLine=null);const e=t.clientX||t.changedTouches&&t.changedTouches[0].clientX,n=t.clientY||t.changedTouches&&t.changedTouches[0].clientY,o=document.elementFromPoint(e,n),s=this.allBoxes.find((t=>t.contains(o)&&t!==this.startBox));this.startBox&&s&&this.createPermanentLine(this.startBox,s)&&this.logBookEvent({event:"matching_connection",div_id:this.divid,act:`connected ${this.startBox.dataset.id} to ${s.dataset.id}`}),this.startBox=null,document.removeEventListener("pointermove",this.updateTempLine),document.removeEventListener("pointerup",this.finishConnection)}}document.addEventListener("runestone:login-complete",(()=>{document.querySelectorAll('[data-component="matching"]').forEach((t=>{if(!t.closest("[data-component=timedAssessment]")){let e={orig:t};window.componentMap[t.id]=new c(e)}}))})),void 0===window.component_factory&&(window.component_factory={}),window.component_factory.matching=function(t){return new c(t)}}}]);
|
|
//# sourceMappingURL=566.383db0dedccd8e8f.js.map
|