diff --git a/amd/build/learningmap.min.js b/amd/build/learningmap.min.js index ff0b2275..ea671068 100644 --- a/amd/build/learningmap.min.js +++ b/amd/build/learningmap.min.js @@ -1,11 +1,11 @@ -define("mod_learningmap/learningmap",["exports","core/notification","core/templates","mod_learningmap/placestore","core/str"],(function(_exports,_notification,_templates,_placestore,Str){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_learningmap/learningmap",["exports","core/notification","core/templates","mod_learningmap/placestore","core/str","core/utils"],(function(_exports,_notification,_templates,_placestore,Str,_utils){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Main module for the learningmap editor * * @module mod_learningmap/learningmap - * @copyright 2025 ISB Bayern + * @copyright 2021-2026 ISB Bayern * @author Stefan Hanauska * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_placestore=_interopRequireDefault(_placestore),Str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Str);const targetPoints_firstPoint=1,targetPoints_secondPoint=2,targetPoints_bezierPoint=3,pathTypes_line=1,pathTypes_quadraticbezier=2;_exports.init=async()=>{var offset,dragel,pathsToUpdateFirstPoint,pathsToUpdateSecondPoint;_templates.default.prefetchTemplates(["mod_learningmap/cssskeleton"]);var selectedElement=null,firstPlace=null,secondPlace=null,lastTarget=null,elementForActivitySelector=null,touchstart=!1,touchend=!1,touchmove=0;let mapdiv=document.getElementById("learningmap-editor-map"),code=document.getElementById("id_svgcode"),svgdoc=(new DOMParser).parseFromString(code.value,"image/svg+xml"),svgnode=svgdoc.querySelector("svg"),activitySetting=document.getElementById("learningmap-activity-setting"),activitySelector=document.getElementById("learningmap-activity-selector"),activityStarting=document.getElementById("learningmap-activity-starting"),activityTarget=document.getElementById("learningmap-activity-target"),activityHiddenWarning=document.getElementById("learningmap-activity-hidden-warning"),advancedSettingsIcon=document.getElementById("learningmap-advanced-settings-icon"),treeView=document.querySelector(".fp-viewbar .fp-vb-tree");treeView&&treeView.setAttribute("style","display: none;");let iconView=document.querySelector(".fp-viewbar .fp-vb-icons");iconView&&setTimeout((()=>{iconView.dispatchEvent(new Event("click"))}),1e3),activitySelector&&(activitySelector.addEventListener("change",(function(){if(_placestore.default.setActivityId(elementForActivitySelector,activitySelector.value),activitySelector.value){let text=document.getElementById("text"+elementForActivitySelector);text&&text.replaceChildren(svgdoc.createCDATASection(activitySelector.querySelector('option[value="'+activitySelector.value+'"]').textContent));let title=document.getElementById("title"+elementForActivitySelector);title&&title.replaceChildren(svgdoc.createCDATASection(activitySelector.querySelector('option[value="'+activitySelector.value+'"]').textContent)),document.getElementById(elementForActivitySelector).classList.remove("learningmap-emptyplace")}else document.getElementById(elementForActivitySelector).classList.add("learningmap-emptyplace");updateActivities(),updateCode()})),activityStarting.addEventListener("change",(function(){activityStarting.checked?_placestore.default.addStartingPlace(elementForActivitySelector):_placestore.default.removeStartingPlace(elementForActivitySelector),updateCode()})),activityTarget.addEventListener("change",(function(){activityTarget.checked?(_placestore.default.addTargetPlace(elementForActivitySelector),document.getElementById(elementForActivitySelector).classList.add("learningmap-targetplace")):(_placestore.default.removeTargetPlace(elementForActivitySelector),document.getElementById(elementForActivitySelector).classList.remove("learningmap-targetplace")),updateCode()})));let placestoreInput=document.getElementsByName("placestore")[0];if(placestoreInput&&_placestore.default.loadJSON(placestoreInput.value),updateActivities(),advancedSettingsIcon){let advancedSettings=document.getElementById("learningmap-advanced-settings");advancedSettingsIcon.addEventListener("click",(function(){null===advancedSettings.getAttribute("hidden")?hideAdvancedSettings():(advancedSettings.removeAttribute("hidden"),hideContextMenu())})),advancedSettingsIcon.addEventListener("keydown",(function(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),advancedSettingsIcon.click())}));let advancedSettingsClose=document.getElementById("learningmap-advanced-settings-close");advancedSettingsClose&&advancedSettingsClose.addEventListener("click",(function(){advancedSettings.setAttribute("hidden","")})),await advancedSettingsLogic("hidepaths",_placestore.default.getHidePaths,_placestore.default.setHidePaths),await advancedSettingsLogic("usecheckmark",_placestore.default.getUseCheckmark,_placestore.default.setUseCheckmark),await advancedSettingsLogic("hover",_placestore.default.getHover,_placestore.default.setHover),await advancedSettingsLogic("pulse",_placestore.default.getPulse,_placestore.default.setPulse),await advancedSettingsLogic("showall",_placestore.default.getShowall,_placestore.default.setShowall),await advancedSettingsLogic("hidestroke",_placestore.default.getHideStroke,_placestore.default.setHideStroke),await advancedSettingsLogic("showtext",_placestore.default.getShowText,_placestore.default.setShowText,(function(){let options=Array.from(activitySelector.getElementsByTagName("option")),places=_placestore.default.getPlaces();for(const place of places)if(null===document.getElementById("text"+place.id)){let content="";for(const option of options)if(option.value==place.linkedActivity){content=option.textContent;break}let placeNode=document.getElementById(place.id),textNode=text("text"+place.id,content,placeNode.cx.baseVal.value,placeNode.cy.baseVal.value);placeNode.parentNode.appendChild(textNode)}})),await advancedSettingsLogic("slicemode",_placestore.default.getSliceMode,_placestore.default.setSliceMode),await advancedSettingsLogic("showwaygone",_placestore.default.getShowWayGone,_placestore.default.setShowWayGone),await advancedSettingsLogic("description",(function(){let title="",desc="",titlenode=svgnode.querySelector("svg>title");titlenode&&(title=titlenode.textContent);let descnode=svgnode.querySelector("svg>desc");descnode&&(desc=descnode.textContent);return{title:title,description:desc}}),(function(placestore,values){let titlenode=svgnode.querySelector("svg>title");titlenode||(titlenode=document.createElementNS("http://www.w3.org/2000/svg","title"),svgnode.appendChild(titlenode));let titlecontent=svgdoc.createCDATASection(values.title);titlenode.replaceChildren(titlecontent),titlenode.setAttribute("id","title-"+placestore.getMapid());let descnode=svgnode.querySelector("svg>desc");descnode||(descnode=document.createElementNS("http://www.w3.org/2000/svg","desc"),svgnode.appendChild(descnode));let desccontent=svgdoc.createCDATASection(values.description);descnode.replaceChildren(desccontent),descnode.setAttribute("id","desc-"+placestore.getMapid())}))}function showContextMenu(e){if(unselectAll(),hideAdvancedSettings(),activitySetting&&null!==document.getElementById(e.target.id))if(e.touches&&(e=e.touches[0]),e.target.classList.contains("learningmap-place")){e.target.classList.add("learningmap-selected-activity-selector");let activityId=_placestore.default.getActivityId(e.target.id),scalingFactor=mapdiv.clientWidth/800;activitySetting.style.setProperty("--pos-x",e.target.cx.baseVal.value*scalingFactor+"px"),activitySetting.style.setProperty("--pos-y",e.target.cy.baseVal.value*scalingFactor+"px"),activitySetting.style.setProperty("--map-width",mapdiv.clientWidth+"px"),activitySetting.style.setProperty("--map-height",mapdiv.clientHeight+"px"),activitySetting.style.display="block",document.getElementById("learningmap-activity-selector").value=activityId,document.getElementById("learningmap-activity-starting").checked=_placestore.default.isStartingPlace(e.target.id),document.getElementById("learningmap-activity-target").checked=_placestore.default.isTargetPlace(e.target.id),elementForActivitySelector=e.target.id,updateActivities()}else hideContextMenu(),hideAdvancedSettings()}function hideContextMenu(){let e=document.getElementById(elementForActivitySelector);e&&e.classList.remove("learningmap-selected-activity-selector"),activitySetting.style.display="none"}colorChooserLogic("stroke","text"),colorChooserLogic("place"),colorChooserLogic("visited"),code&&mapdiv&&mapdiv.replaceChildren(svgnode),refreshBackgroundImage(),function(){let background=document.getElementById("learningmap-background-image");background&&background.addEventListener("load",(function(){background.removeAttribute("height");let height=parseInt(background.getBBox().height),width=background.getBBox().width;_placestore.default.setBackgroundDimensions(width,height),svgnode.setAttribute("viewBox","0 0 "+_placestore.default.width+" "+_placestore.default.height),background.setAttribute("width",width),background.setAttribute("height",height),updateCode()}))}(),updateCode(),function(el){dragel=el,el&&(el.addEventListener("mousedown",startDrag),el.addEventListener("mousemove",drag),el.addEventListener("mouseup",endDrag),el.addEventListener("mouseleave",endDrag),el.addEventListener("touchstart",(function(evt){evt.cancelable&&evt.preventDefault();evt.target.classList.contains("learningmap-draggable")||"text"==evt.target.nodeName||"path"==evt.target.nodeName?(touchstart?(dblclickHandler(evt),touchstart=!1):(touchstart=!0,touchmove=0,touchend=!1,setTimeout((evt=>{touchmove<3&&!touchend&&(evt.touches&&(evt=evt.touches[0]),showContextMenu(evt))}),2e3,evt),setTimeout((()=>{touchstart=!1}),300)),startDrag(evt)):touchstart?(dblclickHandler(evt),touchstart=!1):(touchstart=!0,touchend=!1,touchmove=0,setTimeout((()=>{touchstart=!1}),300))})),el.addEventListener("touchmove",drag),el.addEventListener("touchend",endTouch),el.addEventListener("touchleave",endTouch),el.addEventListener("touchcancel",endTouch));function startDrag(evt){if(evt.cancelable&&evt.preventDefault(),pathsToUpdateFirstPoint=[],pathsToUpdateSecondPoint=[],evt.target.classList.contains("learningmap-draggable"))selectedElement=evt.target,(offset=getMousePosition(evt)).x-=parseInt(selectedElement.getAttributeNS(null,"cx")),offset.y-=parseInt(selectedElement.getAttributeNS(null,"cy")),pathsToUpdateFirstPoint=_placestore.default.getPathsWithFid(selectedElement.id),pathsToUpdateSecondPoint=_placestore.default.getPathsWithSid(selectedElement.id);else if("text"==evt.target.nodeName){let place=(selectedElement=evt.target).parentNode.querySelector(".learningmap-place");(offset=getMousePosition(evt)).x-=parseInt(selectedElement.getAttributeNS(null,"dx"))+place.cx.baseVal.value,offset.y-=parseInt(selectedElement.getAttributeNS(null,"dy"))+place.cy.baseVal.value}else if("path"==evt.target.nodeName){selectedElement=evt.target,offset=getMousePosition(evt);let pathPoint=transformCoordinates(evt.layerX,evt.layerY);offset.x+=pathPoint.x,offset.y+=pathPoint.y}}function drag(evt){if(evt.cancelable&&evt.preventDefault(),touchmove++,selectedElement){var coord=getMousePosition(evt);let cx=coord.x-offset.x,cy=coord.y-offset.y;if("text"==selectedElement.nodeName){let place=selectedElement.parentNode.querySelector(".learningmap-place"),dx=coord.x-offset.x-place.cx.baseVal.value,dy=coord.y-offset.y-place.cy.baseVal.value;selectedElement.setAttributeNS(null,"dx",dx),selectedElement.setAttributeNS(null,"dy",dy)}if("path"==selectedElement.nodeName&&selectedElement.setAttribute("d",updatePathDeclaration(selectedElement.getAttribute("d"),coord.x,coord.y,targetPoints_bezierPoint)),"circle"==selectedElement.nodeName){selectedElement.setAttributeNS(null,"cx",cx),selectedElement.setAttributeNS(null,"cy",cy);let textNode=document.getElementById("text"+selectedElement.id);null!==textNode&&(textNode.setAttributeNS(null,"x",cx),textNode.setAttributeNS(null,"y",cy)),pathsToUpdateFirstPoint.forEach((function(path){let pathNode=document.getElementById(path.id);null!==pathNode&&("path"==pathNode.nodeName?pathNode.setAttribute("d",updatePathDeclaration(pathNode.getAttribute("d"),cx,cy,targetPoints_firstPoint)):(pathNode.setAttribute("x1",cx),pathNode.setAttribute("y1",cy)))})),pathsToUpdateSecondPoint.forEach((function(path){let pathNode=document.getElementById(path.id);null!==pathNode&&("path"==pathNode.nodeName?pathNode.setAttribute("d",updatePathDeclaration(pathNode.getAttribute("d"),cx,cy,targetPoints_secondPoint)):(pathNode.setAttribute("x2",cx),pathNode.setAttribute("y2",cy)))}))}}}function endDrag(evt){evt.cancelable&&evt.preventDefault(),selectedElement=null,unselectAll(),updateCode()}function endTouch(evt){selectedElement=null,touchend=!0,touchmove<3&&touchstart?clickHandler(evt):endDrag(evt),evt.cancelable&&evt.preventDefault()}function updatePathDeclaration(oldDefinition,targetX,targetY){let targetP=arguments.length>3&&void 0!==arguments[3]?arguments[3]:targetPoints_firstPoint,parts=oldDefinition.split(" "),fromX=0,fromY=0,toX=0,toY=0,bezierX=0,bezierY=0,pathType=pathTypes_line;for(let i=0;i2&&void 0!==arguments[2]?arguments[2]:null,text=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,link=document.createElementNS("http://www.w3.org/2000/svg","a");link.setAttribute("id",id),link.setAttribute("xlink:href",""),link.setAttribute("tabindex","0"),link.appendChild(child),null!==title&&link.appendChild(title);null!==text&&link.appendChild(text);return link}(function(x,y,r,classes,id){let circle=document.createElementNS("http://www.w3.org/2000/svg","circle");return circle.setAttribute("class",classes),circle.setAttribute("id",id),circle.setAttribute("cx",x),circle.setAttribute("cy",y),circle.setAttribute("r",r),circle}(cx,cy,10,"learningmap-place learningmap-draggable learningmap-emptyplace",placeId),linkId,function(id){let title=document.createElementNS("http://www.w3.org/2000/svg","title");return title.setAttribute("id",id),title}("title"+placeId),text("text"+placeId,"",cx,cy))),_placestore.default.addPlace(placeId,linkId)}(event):event.target.classList.contains("learningmap-place")?lastTarget==event.target.id?(lastTarget=null,clickHandler(event)):function(event){let place=document.getElementById(event.target.id),parent=place.parentNode;id=event.target.id,_placestore.default.getTouchingPaths(id).forEach((function(e){removePath(e.id)})),_placestore.default.removePlace(event.target.id),parent.removeChild(place),parent.parentNode.removeChild(parent),updateCode();var id}(event):event.target.classList.contains("learningmap-path")&&removePath(event.target.id),updateCode()}function text(id,content,x,y){let text=document.createElementNS("http://www.w3.org/2000/svg","text");text.setAttribute("id",id),text.setAttribute("x",x),text.setAttribute("y",y),text.setAttribute("dx",15),text.setAttribute("dy",15);let textcontent=svgdoc.createCDATASection(content);return text.replaceChildren(textcontent),text}function clickHandler(event){if(event.preventDefault(),hideContextMenu(),hideAdvancedSettings(),event.target.classList.contains("learningmap-place")&&null===selectedElement)if(null===firstPlace)firstPlace=event.target.id,document.getElementById(firstPlace).classList.add("learningmap-selected");else{secondPlace=event.target.id;let fid=parseInt(firstPlace.replace("p","")),sid=parseInt(secondPlace.replace("p",""));if(sid==fid)return;if(sid0){let background=document.getElementById("learningmap-background-image"),backgroundurl=previewimage[0].getAttribute("src").split("?")[0];previewimage[0].getAttribute("src").split("?")[1].includes("&oid=")&&(backgroundurl+="?oid="+previewimage[0].getAttribute("src").split("&oid=")[1]),background.setAttribute("xlink:href",backgroundurl)}}function updateCSS(){_templates.default.renderForPromise("mod_learningmap/cssskeleton",_placestore.default.getPlacestore()).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode("#learningmap-svgstyle",html,js),updateCode(),!0})).catch((ex=>(0,_notification.exception)(ex)))}function updateActivities(){let activities=_placestore.default.getAllActivities(),options=Array.from(activitySelector.getElementsByTagName("option"));activityHiddenWarning.setAttribute("hidden",""),options.forEach((function(n){activities.includes(n.value)?(n.classList.add("learningmap-used-activity"),n.selected&&1==n.getAttribute("data-activity-hidden")&&activityHiddenWarning.removeAttribute("hidden")):n.classList.remove("learningmap-used-activity")}))}function colorChooserLogic(name){let secondValue=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",colorChooser=document.getElementById("learningmap-color-"+name);colorChooser&&(colorChooser.addEventListener("change",(function(){_placestore.default.setColor(name,colorChooser.value),""!=secondValue&&_placestore.default.setColor(secondValue,colorChooser.value),updateCSS()})),colorChooser.value=_placestore.default.getColor(name))}async function advancedSettingsLogic(name,getCall,setCall){let callback=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,settingItem=document.getElementById("learningmap-advanced-setting-"+name);if(settingItem)if("A"==settingItem.nodeName){const descriptionHandler=async()=>{const values=getCall();try{const strings=await Str.get_strings([{key:"titleanddescription",component:"mod_learningmap"},{key:"save",component:"core"}]);return(0,_notification.saveCancel)(strings[0],_templates.default.render("mod_learningmap/"+name+"-modal",values),strings[1],(()=>{let values={};document.querySelectorAll(".mod_learningmap_"+name+"_value").forEach((element=>{values[element.name]=element.value})),setCall(_placestore.default,values),null!==callback&&callback(),updateCSS()}))}catch(ex){return(0,_notification.exception)(ex),null}};settingItem.addEventListener("click",descriptionHandler),settingItem.addEventListener("keypress",(e=>{"Enter"===e.key&&descriptionHandler()}))}else settingItem.checked=getCall.call(_placestore.default),settingItem.addEventListener("change",(function(){setCall.call(_placestore.default,settingItem.checked),null!==callback&&callback(),updateCSS()}))}function hideAdvancedSettings(){document.getElementById("learningmap-advanced-settings").setAttribute("hidden","")}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_placestore=_interopRequireDefault(_placestore),Str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Str);const targetPoints_firstPoint=1,targetPoints_secondPoint=2,targetPoints_bezierPoint=3,pathTypes_line=1,pathTypes_quadraticbezier=2;_exports.init=async()=>{var offset,dragel,pathsToUpdateFirstPoint,pathsToUpdateSecondPoint;_templates.default.prefetchTemplates(["mod_learningmap/cssskeleton"]);var selectedElement=null,firstPlace=null,secondPlace=null,lastTarget=null,elementForActivitySelector=null,touchstart=!1,touchend=!1,touchmove=0;let mapdiv=document.getElementById("learningmap-editor-map"),code=document.getElementById("id_svgcode"),svgdoc=(new DOMParser).parseFromString(code.value,"image/svg+xml"),svgnode=svgdoc.querySelector("svg"),activitySetting=document.getElementById("learningmap-activity-setting"),activitySelector=document.getElementById("learningmap-activity-selector"),activityStarting=document.getElementById("learningmap-activity-starting"),activityTarget=document.getElementById("learningmap-activity-target"),activityHiddenWarning=document.getElementById("learningmap-activity-hidden-warning"),advancedSettingsIcon=document.getElementById("learningmap-advanced-settings-icon"),treeView=document.querySelector(".fp-viewbar .fp-vb-tree");treeView&&treeView.setAttribute("style","display: none;");let iconView=document.querySelector(".fp-viewbar .fp-vb-icons");iconView&&setTimeout((()=>{iconView.dispatchEvent(new Event("click"))}),1e3),activitySelector&&(activitySelector.addEventListener("change",(function(){if(_placestore.default.setActivityId(elementForActivitySelector,activitySelector.value),activitySelector.value){let text=document.getElementById("text"+elementForActivitySelector);text&&text.replaceChildren(svgdoc.createTextNode(activitySelector.querySelector('option[value="'+activitySelector.value+'"]').textContent));let title=document.getElementById("title"+elementForActivitySelector);title&&title.replaceChildren(svgdoc.createTextNode(activitySelector.querySelector('option[value="'+activitySelector.value+'"]').textContent)),document.getElementById(elementForActivitySelector).classList.remove("learningmap-emptyplace")}else document.getElementById(elementForActivitySelector).classList.add("learningmap-emptyplace");updateActivities(),updateCode()})),activityStarting.addEventListener("change",(function(){activityStarting.checked?_placestore.default.addStartingPlace(elementForActivitySelector):_placestore.default.removeStartingPlace(elementForActivitySelector),updateCode()})),activityTarget.addEventListener("change",(function(){activityTarget.checked?(_placestore.default.addTargetPlace(elementForActivitySelector),document.getElementById(elementForActivitySelector).classList.add("learningmap-targetplace")):(_placestore.default.removeTargetPlace(elementForActivitySelector),document.getElementById(elementForActivitySelector).classList.remove("learningmap-targetplace")),updateCode()})));let placestoreInput=document.getElementsByName("placestore")[0];if(placestoreInput&&_placestore.default.loadJSON(placestoreInput.value),updateActivities(),advancedSettingsIcon){let advancedSettings=document.getElementById("learningmap-advanced-settings");advancedSettingsIcon.addEventListener("click",(function(){null===advancedSettings.getAttribute("hidden")?hideAdvancedSettings():(advancedSettings.removeAttribute("hidden"),hideContextMenu())})),advancedSettingsIcon.addEventListener("keydown",(function(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),advancedSettingsIcon.click())}));let advancedSettingsClose=document.getElementById("learningmap-advanced-settings-close");advancedSettingsClose&&advancedSettingsClose.addEventListener("click",(function(){advancedSettings.setAttribute("hidden","")})),await advancedSettingsLogic("hidepaths",_placestore.default.getHidePaths,_placestore.default.setHidePaths),await advancedSettingsLogic("usecheckmark",_placestore.default.getUseCheckmark,_placestore.default.setUseCheckmark),await advancedSettingsLogic("hover",_placestore.default.getHover,_placestore.default.setHover),await advancedSettingsLogic("pulse",_placestore.default.getPulse,_placestore.default.setPulse),await advancedSettingsLogic("showall",_placestore.default.getShowall,_placestore.default.setShowall),await advancedSettingsLogic("hidestroke",_placestore.default.getHideStroke,_placestore.default.setHideStroke),await advancedSettingsLogic("showtext",_placestore.default.getShowText,_placestore.default.setShowText,(function(){let options=Array.from(activitySelector.getElementsByTagName("option")),places=_placestore.default.getPlaces();for(const place of places)if(null===document.getElementById("text"+place.id)){let content="";for(const option of options)if(option.value==place.linkedActivity){content=option.textContent;break}let placeNode=document.getElementById(place.id),textGroup=document.getElementById("textsGroup-"+_placestore.default.getMapid()),textNode=text("text"+place.id,content,placeNode.cx.baseVal.value,placeNode.cy.baseVal.value);textGroup.appendChild(textNode)}})),await advancedSettingsLogic("slicemode",_placestore.default.getSliceMode,_placestore.default.setSliceMode),await advancedSettingsLogic("showwaygone",_placestore.default.getShowWayGone,_placestore.default.setShowWayGone),await advancedSettingsLogic("description",(function(){let title="",desc="",titlenode=svgnode.querySelector("svg>title");titlenode&&(title=titlenode.textContent);let descnode=svgnode.querySelector("svg>desc");descnode&&(desc=descnode.textContent);return{title:title,description:desc}}),(function(placestore,values){let titlenode=svgnode.querySelector("svg>title");titlenode||(titlenode=document.createElementNS("http://www.w3.org/2000/svg","title"),svgnode.appendChild(titlenode));let titlecontent=svgdoc.createTextNode(values.title);titlenode.replaceChildren(titlecontent),titlenode.setAttribute("id","title-"+placestore.getMapid());let descnode=svgnode.querySelector("svg>desc");descnode||(descnode=document.createElementNS("http://www.w3.org/2000/svg","desc"),svgnode.appendChild(descnode));let desccontent=svgdoc.createTextNode(values.description);descnode.replaceChildren(desccontent),descnode.setAttribute("id","desc-"+placestore.getMapid())}))}function showContextMenu(e){if(unselectAll(),hideAdvancedSettings(),activitySetting&&null!==document.getElementById(e.target.id))if(e.touches&&(e=e.touches[0]),e.target.classList.contains("learningmap-place")){e.target.classList.add("learningmap-selected-activity-selector");let activityId=_placestore.default.getActivityId(e.target.id),scalingFactor=mapdiv.clientWidth/800;activitySetting.style.setProperty("--pos-x",e.target.cx.baseVal.value*scalingFactor+"px"),activitySetting.style.setProperty("--pos-y",e.target.cy.baseVal.value*scalingFactor+"px"),activitySetting.style.setProperty("--map-width",mapdiv.clientWidth+"px"),activitySetting.style.setProperty("--map-height",mapdiv.clientHeight+"px"),activitySetting.style.display="block",document.getElementById("learningmap-activity-selector").value=activityId,document.getElementById("learningmap-activity-starting").checked=_placestore.default.isStartingPlace(e.target.id),document.getElementById("learningmap-activity-target").checked=_placestore.default.isTargetPlace(e.target.id),elementForActivitySelector=e.target.id,updateActivities()}else hideContextMenu(),hideAdvancedSettings()}function hideContextMenu(){let e=document.getElementById(elementForActivitySelector);e&&e.classList.remove("learningmap-selected-activity-selector"),activitySetting.style.display="none"}colorChooserLogic("stroke","text"),colorChooserLogic("place"),colorChooserLogic("visited"),code&&mapdiv&&mapdiv.replaceChildren(svgnode),refreshBackgroundImage(),function(){let background=document.getElementById("learningmap-background-image");background||(background=document.getElementById("learningmap-background-image-"+_placestore.default.getMapid()));background&&background.addEventListener("load",(function(){background.removeAttribute("height");let height=parseInt(background.getBBox().height),width=background.getBBox().width;_placestore.default.setBackgroundDimensions(width,height),svgnode.setAttribute("viewBox","0 0 "+_placestore.default.width+" "+_placestore.default.height),background.setAttribute("width",width),background.setAttribute("height",height),updateCode()}))}(),updateCode(),function(el){dragel=el,el&&(el.addEventListener("mousedown",startDrag),el.addEventListener("mousemove",(0,_utils.debounce)(drag,5)),el.addEventListener("mouseup",endDrag),el.addEventListener("mouseleave",endDrag),el.addEventListener("touchstart",(function(evt){evt.cancelable&&evt.preventDefault();evt.target.classList.contains("learningmap-draggable")||"text"==evt.target.nodeName||"path"==evt.target.nodeName?(touchstart?(dblclickHandler(evt),touchstart=!1):(touchstart=!0,touchmove=0,touchend=!1,setTimeout((evt=>{touchmove<3&&!touchend&&(evt.touches&&(evt=evt.touches[0]),showContextMenu(evt))}),2e3,evt),setTimeout((()=>{touchstart=!1}),300)),startDrag(evt)):touchstart?(dblclickHandler(evt),touchstart=!1):(touchstart=!0,touchend=!1,touchmove=0,setTimeout((()=>{touchstart=!1}),300))})),el.addEventListener("touchmove",(0,_utils.debounce)(drag,5)),el.addEventListener("touchend",endTouch),el.addEventListener("touchleave",endTouch),el.addEventListener("touchcancel",endTouch));function startDrag(evt){if(evt.cancelable&&evt.preventDefault(),pathsToUpdateFirstPoint=[],pathsToUpdateSecondPoint=[],evt.target.classList.contains("learningmap-draggable"))selectedElement=evt.target,(offset=getMousePosition(evt)).x-=parseInt(selectedElement.getAttributeNS(null,"cx")),offset.y-=parseInt(selectedElement.getAttributeNS(null,"cy")),pathsToUpdateFirstPoint=_placestore.default.getPathsWithFid(selectedElement.id),pathsToUpdateSecondPoint=_placestore.default.getPathsWithSid(selectedElement.id);else if("text"==evt.target.nodeName){let place=findPlaceForText((selectedElement=evt.target).id);(offset=getMousePosition(evt)).x-=parseInt(selectedElement.getAttributeNS(null,"dx"))+place.cx.baseVal.value,offset.y-=parseInt(selectedElement.getAttributeNS(null,"dy"))+place.cy.baseVal.value}else if("path"==evt.target.nodeName){selectedElement=evt.target,offset=getMousePosition(evt);let pathPoint=transformCoordinates(evt.layerX,evt.layerY);offset.x+=pathPoint.x,offset.y+=pathPoint.y}}function drag(evt){if(evt.cancelable&&evt.preventDefault(),touchmove++,selectedElement){var coord=getMousePosition(evt);let cx=coord.x-offset.x,cy=coord.y-offset.y;if("text"==selectedElement.nodeName){let place=findPlaceForText(selectedElement.id),dx=coord.x-offset.x-place.cx.baseVal.value,dy=coord.y-offset.y-place.cy.baseVal.value;selectedElement.setAttributeNS(null,"dx",dx),selectedElement.setAttributeNS(null,"dy",dy)}if("path"==selectedElement.nodeName&&selectedElement.setAttribute("d",updatePathDeclaration(selectedElement.getAttribute("d"),coord.x,coord.y,targetPoints_bezierPoint)),"circle"==selectedElement.nodeName){selectedElement.setAttributeNS(null,"cx",cx),selectedElement.setAttributeNS(null,"cy",cy);let textNode=document.getElementById("text"+selectedElement.id);null!==textNode&&(textNode.setAttributeNS(null,"x",cx),textNode.setAttributeNS(null,"y",cy)),pathsToUpdateFirstPoint.forEach((function(path){let pathNode=document.getElementById(path.id);null!==pathNode&&("path"==pathNode.nodeName?pathNode.setAttribute("d",updatePathDeclaration(pathNode.getAttribute("d"),cx,cy,targetPoints_firstPoint)):(pathNode.setAttribute("x1",cx),pathNode.setAttribute("y1",cy)))})),pathsToUpdateSecondPoint.forEach((function(path){let pathNode=document.getElementById(path.id);null!==pathNode&&("path"==pathNode.nodeName?pathNode.setAttribute("d",updatePathDeclaration(pathNode.getAttribute("d"),cx,cy,targetPoints_secondPoint)):(pathNode.setAttribute("x2",cx),pathNode.setAttribute("y2",cy)))}))}}}function endDrag(evt){evt.cancelable&&evt.preventDefault(),selectedElement=null,unselectAll(),updateCode()}function endTouch(evt){selectedElement=null,touchend=!0,touchmove<3&&touchstart?clickHandler(evt):endDrag(evt),evt.cancelable&&evt.preventDefault()}function updatePathDeclaration(oldDefinition,targetX,targetY){let targetP=arguments.length>3&&void 0!==arguments[3]?arguments[3]:targetPoints_firstPoint,parts=oldDefinition.split(" "),fromX=0,fromY=0,toX=0,toY=0,bezierX=0,bezierY=0,pathType=pathTypes_line;for(let i=0;i2&&void 0!==arguments[2]?arguments[2]:null,link=document.createElementNS("http://www.w3.org/2000/svg","a");link.setAttribute("id",id),link.setAttribute("xlink:href",""),link.setAttribute("tabindex","0"),link.appendChild(child),null!==title&&link.appendChild(title);return link}(function(x,y,r,classes,id){let circle=document.createElementNS("http://www.w3.org/2000/svg","circle");return circle.setAttribute("class",classes),circle.setAttribute("id",id),circle.setAttribute("cx",x),circle.setAttribute("cy",y),circle.setAttribute("r",r),circle}(cx,cy,10,"learningmap-place learningmap-draggable learningmap-emptyplace",placeId),linkId,function(id){let title=document.createElementNS("http://www.w3.org/2000/svg","title");return title.setAttribute("id",id),title}("title"+placeId))),textgroup.appendChild(text("text"+placeId,"",cx,cy)),_placestore.default.addPlace(placeId,linkId)}(event):event.target.classList.contains("learningmap-place")?lastTarget==event.target.id?(lastTarget=null,clickHandler(event)):function(event){let place=document.getElementById(event.target.id),parent=place.parentNode;id=event.target.id,_placestore.default.getTouchingPaths(id).forEach((function(e){removePath(e.id)})),_placestore.default.removePlace(event.target.id),parent.removeChild(place),parent.parentNode.removeChild(parent);var id;let textNode=document.getElementById("text"+event.target.id);textNode&&textNode.parentNode.removeChild(textNode);updateCode()}(event):event.target.classList.contains("learningmap-path")&&removePath(event.target.id),updateCode()}function text(id,content,x,y){let text=document.createElementNS("http://www.w3.org/2000/svg","text");text.setAttribute("id",id),text.setAttribute("x",x),text.setAttribute("y",y),text.setAttribute("dx",15),text.setAttribute("dy",15);let textcontent=svgdoc.createTextNode(content);return text.replaceChildren(textcontent),text}function clickHandler(event){if(event.preventDefault(),hideContextMenu(),hideAdvancedSettings(),event.target.classList.contains("learningmap-place")&&null===selectedElement)if(null===firstPlace)firstPlace=event.target.id,document.getElementById(firstPlace).classList.add("learningmap-selected");else{secondPlace=event.target.id;let fid=parseInt(firstPlace.replace("p","")),sid=parseInt(secondPlace.replace("p",""));if(sid==fid)return;if(sid0){let background=document.getElementById("learningmap-background-image");background||(background=document.getElementById("learningmap-background-image-"+_placestore.default.getMapid()));let backgroundurl=previewimage[0].getAttribute("src").split("?")[0];previewimage[0].getAttribute("src").split("?")[1].includes("&oid=")&&(backgroundurl+="?oid="+previewimage[0].getAttribute("src").split("&oid=")[1]),background.setAttribute("xlink:href",backgroundurl)}}function updateCSS(){_templates.default.renderForPromise("mod_learningmap/cssskeleton",_placestore.default.getPlacestore()).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode("#learningmap-svgstyle",html,js),updateCode(),!0})).catch((ex=>(0,_notification.exception)(ex)))}function updateActivities(){let activities=_placestore.default.getAllActivities(),options=Array.from(activitySelector.getElementsByTagName("option"));activityHiddenWarning.setAttribute("hidden",""),options.forEach((function(n){activities.includes(n.value)?(n.classList.add("learningmap-used-activity"),n.selected&&1==n.getAttribute("data-activity-hidden")&&activityHiddenWarning.removeAttribute("hidden")):n.classList.remove("learningmap-used-activity")}))}function colorChooserLogic(name){let secondValue=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",colorChooser=document.getElementById("learningmap-color-"+name);colorChooser&&(colorChooser.addEventListener("change",(function(){_placestore.default.setColor(name,colorChooser.value),""!=secondValue&&_placestore.default.setColor(secondValue,colorChooser.value),updateCSS()})),colorChooser.value=_placestore.default.getColor(name))}async function advancedSettingsLogic(name,getCall,setCall){let callback=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,settingItem=document.getElementById("learningmap-advanced-setting-"+name);if(settingItem)if("A"==settingItem.nodeName){const descriptionHandler=async()=>{const values=getCall();try{const strings=await Str.get_strings([{key:"titleanddescription",component:"mod_learningmap"},{key:"save",component:"core"}]);return(0,_notification.saveCancel)(strings[0],_templates.default.render("mod_learningmap/"+name+"-modal",values),strings[1],(()=>{let values={};document.querySelectorAll(".mod_learningmap_"+name+"_value").forEach((element=>{values[element.name]=element.value})),setCall(_placestore.default,values),null!==callback&&callback(),updateCSS()}))}catch(ex){return(0,_notification.exception)(ex),null}};settingItem.addEventListener("click",descriptionHandler),settingItem.addEventListener("keypress",(e=>{"Enter"===e.key&&descriptionHandler()}))}else settingItem.checked=getCall.call(_placestore.default),settingItem.addEventListener("change",(function(){setCall.call(_placestore.default,settingItem.checked),null!==callback&&callback(),updateCSS()}))}function hideAdvancedSettings(){document.getElementById("learningmap-advanced-settings").setAttribute("hidden","")}function findPlaceForText(textId){let placename=textId.replace("text","");return svgnode.getElementById(placename)}}})); //# sourceMappingURL=learningmap.min.js.map \ No newline at end of file diff --git a/amd/build/learningmap.min.js.map b/amd/build/learningmap.min.js.map index 02a49dbb..1a90bd24 100644 --- a/amd/build/learningmap.min.js.map +++ b/amd/build/learningmap.min.js.map @@ -1 +1 @@ -{"version":3,"file":"learningmap.min.js","sources":["../src/learningmap.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main module for the learningmap editor\n *\n * @module mod_learningmap/learningmap\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {exception as displayException, saveCancel} from 'core/notification';\nimport Templates from 'core/templates';\nimport placestore from 'mod_learningmap/placestore';\nimport * as Str from 'core/str';\n\nconst circleRadius = 10;\n\n// Constants for updatePathDeclaration.\nconst targetPoints = {\n firstPoint: 1,\n secondPoint: 2,\n bezierPoint: 3,\n};\n\nconst pathTypes = {\n line: 1,\n quadraticbezier: 2,\n};\n\nexport const init = async() => {\n // Load the needed template on startup for better execution speed.\n Templates.prefetchTemplates(['mod_learningmap/cssskeleton']);\n\n // Variable for storing the mouse offset\n var offset;\n\n // Variable for draggable element\n var dragel;\n\n // Variables for storing the paths that need update of the first or\n // the second coordinates.\n var pathsToUpdateFirstPoint, pathsToUpdateSecondPoint;\n\n // Variables for handling the currently selected elements\n var selectedElement = null,\n firstPlace = null,\n secondPlace = null,\n lastTarget = null;\n\n // Variable for storing the selected element for the activity selector\n var elementForActivitySelector = null;\n\n // Variables for simulating double click on touch devices, set when the\n // corresponding events are handled\n var touchstart = false;\n var touchend = false;\n // Counter for touchmove events\n var touchmove = 0;\n\n // DOM nodes for the editor\n let mapdiv = document.getElementById('learningmap-editor-map');\n let code = document.getElementById('id_svgcode');\n\n let svgdoc = new DOMParser().parseFromString(code.value, 'image/svg+xml');\n let svgnode = svgdoc.querySelector('svg');\n\n // DOM nodes for the activity selector\n let activitySetting = document.getElementById('learningmap-activity-setting');\n let activitySelector = document.getElementById('learningmap-activity-selector');\n let activityStarting = document.getElementById('learningmap-activity-starting');\n let activityTarget = document.getElementById('learningmap-activity-target');\n let activityHiddenWarning = document.getElementById('learningmap-activity-hidden-warning');\n let advancedSettingsIcon = document.getElementById('learningmap-advanced-settings-icon');\n\n // Hide tree view as there is no preview file we can attach to\n let treeView = document.querySelector('.fp-viewbar .fp-vb-tree');\n if (treeView) {\n treeView.setAttribute('style', 'display: none;');\n }\n\n // Trigger click event on icon view to ensure that tree view is not active.\n let iconView = document.querySelector('.fp-viewbar .fp-vb-icons');\n if (iconView) {\n // Handle possible delay in form loading.\n setTimeout(() => {\n iconView.dispatchEvent(new Event('click'));\n }, 1000);\n }\n\n // Attach listeners to the activity selector\n if (activitySelector) {\n // Show places that are not linked to an activity\n activitySelector.addEventListener('change', function() {\n placestore.setActivityId(elementForActivitySelector, activitySelector.value);\n if (activitySelector.value) {\n let text = document.getElementById('text' + elementForActivitySelector);\n if (text) {\n text.replaceChildren(svgdoc.createCDATASection(\n activitySelector.querySelector('option[value=\"' + activitySelector.value + '\"]').textContent\n ));\n }\n let title = document.getElementById('title' + elementForActivitySelector);\n if (title) {\n title.replaceChildren(svgdoc.createCDATASection(\n activitySelector.querySelector('option[value=\"' + activitySelector.value + '\"]').textContent\n ));\n }\n document.getElementById(elementForActivitySelector).classList.remove('learningmap-emptyplace');\n } else {\n document.getElementById(elementForActivitySelector).classList.add('learningmap-emptyplace');\n }\n updateActivities();\n updateCode();\n });\n // Add / remove a place to the starting places array\n activityStarting.addEventListener('change', function() {\n if (activityStarting.checked) {\n placestore.addStartingPlace(elementForActivitySelector);\n } else {\n placestore.removeStartingPlace(elementForActivitySelector);\n }\n updateCode();\n });\n // Add / remove a place to the target places array\n activityTarget.addEventListener('change', function() {\n if (activityTarget.checked) {\n placestore.addTargetPlace(elementForActivitySelector);\n document.getElementById(elementForActivitySelector).classList.add('learningmap-targetplace');\n } else {\n placestore.removeTargetPlace(elementForActivitySelector);\n document.getElementById(elementForActivitySelector).classList.remove('learningmap-targetplace');\n }\n updateCode();\n });\n }\n\n // Load placestore values from the hidden input field\n let placestoreInput = document.getElementsByName('placestore')[0];\n if (placestoreInput) {\n placestore.loadJSON(placestoreInput.value);\n }\n\n // Mark all activities in the placestore as \"used\".\n updateActivities();\n\n // Attach listeners to the advanced settings div\n if (advancedSettingsIcon) {\n let advancedSettings = document.getElementById('learningmap-advanced-settings');\n advancedSettingsIcon.addEventListener('click', function() {\n if (advancedSettings.getAttribute('hidden') === null) {\n hideAdvancedSettings();\n } else {\n advancedSettings.removeAttribute('hidden');\n hideContextMenu();\n }\n });\n advancedSettingsIcon.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n advancedSettingsIcon.click();\n }\n });\n let advancedSettingsClose = document.getElementById('learningmap-advanced-settings-close');\n if (advancedSettingsClose) {\n advancedSettingsClose.addEventListener('click', function() {\n advancedSettings.setAttribute('hidden', '');\n });\n }\n\n await advancedSettingsLogic('hidepaths', placestore.getHidePaths, placestore.setHidePaths);\n await advancedSettingsLogic('usecheckmark', placestore.getUseCheckmark, placestore.setUseCheckmark);\n await advancedSettingsLogic('hover', placestore.getHover, placestore.setHover);\n await advancedSettingsLogic('pulse', placestore.getPulse, placestore.setPulse);\n await advancedSettingsLogic('showall', placestore.getShowall, placestore.setShowall);\n await advancedSettingsLogic('hidestroke', placestore.getHideStroke, placestore.setHideStroke);\n await advancedSettingsLogic('showtext', placestore.getShowText, placestore.setShowText, fixPlaceLabels);\n await advancedSettingsLogic('slicemode', placestore.getSliceMode, placestore.setSliceMode);\n await advancedSettingsLogic('showwaygone', placestore.getShowWayGone, placestore.setShowWayGone);\n await advancedSettingsLogic('description', getTitleAndDesc, setTitleAndDesc);\n }\n\n // Attach listener to the color choosers\n colorChooserLogic('stroke', 'text');\n colorChooserLogic('place');\n colorChooserLogic('visited');\n\n // Get SVG code from the (hidden) textarea field\n if (code && mapdiv) {\n mapdiv.replaceChildren(svgnode);\n }\n // Reload background image to get the correct width and height values\n refreshBackgroundImage();\n registerBackgroundListener();\n updateCode();\n\n // Enable dragging of places\n makeDraggable(svgnode);\n\n // Refresh stylesheet values from placestore\n updateCSS();\n\n // Add listeners for clicking and context menu\n if (mapdiv) {\n mapdiv.addEventListener('dblclick', dblclickHandler);\n mapdiv.addEventListener('click', clickHandler);\n\n mapdiv.addEventListener('contextmenu', function(e) {\n e.preventDefault();\n showContextMenu(e);\n }, false);\n }\n /**\n * Shows the context menu at the current mouse position\n * @param {*} e\n */\n function showContextMenu(e) {\n unselectAll();\n hideAdvancedSettings();\n // Check for the existence of the target (could have vanished since the event started).\n if (activitySetting && document.getElementById(e.target.id) !== null) {\n if (e.touches) {\n e = e.touches[0];\n }\n if (e.target.classList.contains('learningmap-place')) {\n e.target.classList.add('learningmap-selected-activity-selector');\n let activityId = placestore.getActivityId(e.target.id);\n let scalingFactor = mapdiv.clientWidth / 800;\n activitySetting.style.setProperty('--pos-x', e.target.cx.baseVal.value * scalingFactor + 'px');\n activitySetting.style.setProperty('--pos-y', e.target.cy.baseVal.value * scalingFactor + 'px');\n activitySetting.style.setProperty('--map-width', mapdiv.clientWidth + 'px');\n activitySetting.style.setProperty('--map-height', mapdiv.clientHeight + 'px');\n activitySetting.style.display = 'block';\n document.getElementById('learningmap-activity-selector').value = activityId;\n document.getElementById('learningmap-activity-starting').checked = placestore.isStartingPlace(e.target.id);\n document.getElementById('learningmap-activity-target').checked = placestore.isTargetPlace(e.target.id);\n elementForActivitySelector = e.target.id;\n updateActivities();\n } else {\n hideContextMenu();\n hideAdvancedSettings();\n }\n }\n }\n\n /**\n * Hides the context menu\n */\n function hideContextMenu() {\n let e = document.getElementById(elementForActivitySelector);\n if (e) {\n e.classList.remove('learningmap-selected-activity-selector');\n }\n activitySetting.style.display = 'none';\n }\n\n let backgroundfileNode = document.getElementById('id_backgroundfile_fieldset');\n if (backgroundfileNode) {\n let observer = new MutationObserver(refreshBackgroundImage);\n observer.observe(backgroundfileNode, {attributes: true, childList: true, subtree: true});\n }\n\n /**\n * Helper function for getting the right coordinates from the mouse\n * @param {*} evt\n * @returns {object}\n */\n function getMousePosition(evt) {\n if (evt.touches) {\n evt = evt.touches[0];\n }\n return transformCoordinates(evt.clientX, evt.clientY);\n }\n\n /**\n * Transforms client coordinates to SVG coordinates\n * @param {number} x x coordinate to transform\n * @param {number} y y coordinate to transform\n * @returns {object} Object containing transformed x and y coordinate\n */\n function transformCoordinates(x, y) {\n var CTM = dragel.getScreenCTM();\n return {\n x: (x - CTM.e) / CTM.a,\n y: (y - CTM.f) / CTM.d\n };\n }\n\n /**\n * Enables dragging on an DOM node\n * @param {*} el\n */\n function makeDraggable(el) {\n dragel = el;\n if (el) {\n el.addEventListener('mousedown', startDrag);\n el.addEventListener('mousemove', drag);\n el.addEventListener('mouseup', endDrag);\n el.addEventListener('mouseleave', endDrag);\n el.addEventListener('touchstart', startTouch);\n el.addEventListener('touchmove', drag);\n el.addEventListener('touchend', endTouch);\n el.addEventListener('touchleave', endTouch);\n el.addEventListener('touchcancel', endTouch);\n }\n\n /**\n * Function called whenn dragging starts.\n * @param {*} evt\n */\n function startDrag(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n pathsToUpdateFirstPoint = [];\n pathsToUpdateSecondPoint = [];\n if (evt.target.classList.contains('learningmap-draggable')) {\n selectedElement = evt.target;\n offset = getMousePosition(evt);\n offset.x -= parseInt(selectedElement.getAttributeNS(null, \"cx\"));\n offset.y -= parseInt(selectedElement.getAttributeNS(null, \"cy\"));\n // Get paths that need to be updated.\n pathsToUpdateFirstPoint = placestore.getPathsWithFid(selectedElement.id);\n pathsToUpdateSecondPoint = placestore.getPathsWithSid(selectedElement.id);\n } else if (evt.target.nodeName == 'text') {\n selectedElement = evt.target;\n let place = selectedElement.parentNode.querySelector('.learningmap-place');\n offset = getMousePosition(evt);\n offset.x -= parseInt(selectedElement.getAttributeNS(null, \"dx\")) + place.cx.baseVal.value;\n offset.y -= parseInt(selectedElement.getAttributeNS(null, \"dy\")) + place.cy.baseVal.value;\n } else if (evt.target.nodeName == 'path') {\n selectedElement = evt.target;\n offset = getMousePosition(evt);\n let pathPoint = transformCoordinates(evt.layerX, evt.layerY);\n offset.x += pathPoint.x;\n offset.y += pathPoint.y;\n }\n }\n\n /**\n * Function called during dragging. Continuously updates circles center coordinates and the\n * coordinates of the touching paths.\n * @param {*} evt\n */\n function drag(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n // Count touchmove events\n touchmove++;\n if (selectedElement) {\n var coord = getMousePosition(evt);\n let cx = coord.x - offset.x;\n let cy = coord.y - offset.y;\n if (selectedElement.nodeName == 'text') {\n let place = selectedElement.parentNode.querySelector('.learningmap-place');\n // Calculate the delta from the current mouse position to the corresponding place.\n // coord: current mouse position\n // offset: delta from the mouse position to the coordinates of the text node\n let dx = coord.x - offset.x - place.cx.baseVal.value;\n let dy = coord.y - offset.y - place.cy.baseVal.value;\n selectedElement.setAttributeNS(null, \"dx\", dx);\n selectedElement.setAttributeNS(null, \"dy\", dy);\n }\n if (selectedElement.nodeName == 'path') {\n selectedElement.setAttribute(\n 'd',\n updatePathDeclaration(selectedElement.getAttribute('d'), coord.x, coord.y, targetPoints.bezierPoint)\n );\n }\n if (selectedElement.nodeName == 'circle') {\n selectedElement.setAttributeNS(null, \"cx\", cx);\n selectedElement.setAttributeNS(null, \"cy\", cy);\n let textNode = document.getElementById('text' + selectedElement.id);\n if (textNode !== null) {\n textNode.setAttributeNS(null, 'x', cx);\n textNode.setAttributeNS(null, 'y', cy);\n }\n pathsToUpdateFirstPoint.forEach(function(path) {\n let pathNode = document.getElementById(path.id);\n if (pathNode !== null) {\n if (pathNode.nodeName == 'path') {\n pathNode.setAttribute(\n 'd',\n updatePathDeclaration(pathNode.getAttribute('d'), cx, cy, targetPoints.firstPoint)\n );\n } else {\n pathNode.setAttribute('x1', cx);\n pathNode.setAttribute('y1', cy);\n }\n }\n });\n\n pathsToUpdateSecondPoint.forEach(function(path) {\n let pathNode = document.getElementById(path.id);\n if (pathNode !== null) {\n if (pathNode.nodeName == 'path') {\n pathNode.setAttribute(\n 'd',\n updatePathDeclaration(pathNode.getAttribute('d'), cx, cy, targetPoints.secondPoint)\n );\n } else {\n pathNode.setAttribute('x2', cx);\n pathNode.setAttribute('y2', cy);\n }\n }\n });\n }\n }\n }\n\n /**\n * Function called when dragging ends.\n * @param {*} evt\n */\n function endDrag(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n selectedElement = null;\n unselectAll();\n updateCode();\n }\n\n /**\n * Function called when touchstart event occurs.\n * @param {*} evt\n */\n function startTouch(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n if (\n evt.target.classList.contains('learningmap-draggable') ||\n evt.target.nodeName == 'text' ||\n evt.target.nodeName == 'path'\n ) {\n if (!touchstart) {\n touchstart = true;\n touchmove = 0;\n touchend = false;\n setTimeout(\n (evt) => {\n if (touchmove < 3 && !touchend) {\n if (evt.touches) {\n evt = evt.touches[0];\n }\n showContextMenu(evt);\n }\n },\n 2000,\n evt\n );\n setTimeout(\n () => {\n touchstart = false;\n },\n 300);\n } else {\n dblclickHandler(evt);\n touchstart = false;\n }\n startDrag(evt);\n } else {\n if (!touchstart) {\n touchstart = true;\n touchend = false;\n touchmove = 0;\n setTimeout(\n () => {\n touchstart = false;\n },\n 300);\n } else {\n dblclickHandler(evt);\n touchstart = false;\n }\n }\n }\n\n /**\n * Function called when touchend, touchleave or touchcancel event occurs.\n * @param {*} evt\n */\n function endTouch(evt) {\n selectedElement = null;\n touchend = true;\n // If there was only a small move (<3 move events), this also counts as a click.\n if (touchmove < 3 && touchstart) {\n clickHandler(evt);\n } else {\n endDrag(evt);\n }\n if (evt.cancelable) {\n evt.preventDefault();\n }\n }\n\n /**\n * Updates the path declaration of lines and quadratic bezier curves setting one of the points.\n * @param {string} oldDefinition SVG path definition string\n * @param {number} targetX x coordinate of the point to set\n * @param {number} targetY y coordinate of the point to set\n * @param {number} targetP Which point to change (you can use the targetPoints constants here)\n * @returns {string} Updated SVG path definition\n */\n function updatePathDeclaration(oldDefinition, targetX, targetY, targetP = targetPoints.firstPoint) {\n let parts = oldDefinition.split(' ');\n let fromX = 0;\n let fromY = 0;\n let toX = 0;\n let toY = 0;\n let bezierX = 0;\n let bezierY = 0;\n let pathType = pathTypes.line;\n\n // The d attribute of an SVG path in a learning map can have two different formats (in this version):\n // \"M x1 y1 L x2 y2\" Line from x1, y1 to x2, y2\n // \"M x1 y2 Q x3 y3 x2 y2\" Quadratic bezier curve inside the triangle defined by x1, y1, x2, y2 and x3, y3.\n for (let i = 0; i < parts.length; i++) {\n // Every path contains the first point in that way.\n if (parts[i] == 'M') {\n fromX = parseInt(parts[i + 1]);\n fromY = parseInt(parts[i + 2]);\n i += 2;\n }\n // This path is a direct line, so there are only two points in total.\n if (parts[i] == 'L') {\n toX = parseInt(parts[i + 1]);\n toY = parseInt(parts[i + 2]);\n i += 2;\n }\n // This path is a bezier curve, there are three points in total.\n if (parts[i] == 'Q') {\n bezierX = parseInt(parts[i + 1]);\n bezierY = parseInt(parts[i + 2]);\n toX = parseInt(parts[i + 3]);\n toY = parseInt(parts[i + 4]);\n i += 4;\n pathType = pathTypes.quadraticbezier;\n }\n }\n\n switch (targetP) {\n case targetPoints.firstPoint:\n fromX = targetX;\n fromY = targetY;\n break;\n case targetPoints.secondPoint:\n toX = targetX;\n toY = targetY;\n break;\n case targetPoints.bezierPoint:\n // Calculate the third triangle point for the bezier curve.\n bezierX = targetX * 2 - (fromX + toX) * 0.5;\n bezierY = targetY * 2 - (fromY + toY) * 0.5;\n pathType = pathTypes.quadraticbezier;\n break;\n }\n\n if (pathType == pathTypes.quadraticbezier) {\n return 'M ' + fromX + ' ' + fromY + ' Q ' + bezierX + ' ' + bezierY + ', ' + toX + ' ' + toY;\n } else {\n return 'M ' + fromX + ' ' + fromY + ' L ' + toX + ' ' + toY;\n }\n }\n }\n\n /**\n * Updates the form fields for the SVG code and the placestore from the editor.\n */\n function updateCode() {\n if (code && mapdiv) {\n code.innerHTML = mapdiv.innerHTML;\n }\n if (placestoreInput) {\n document.getElementsByName('placestore')[0].value = JSON.stringify(placestore.getPlacestore());\n }\n }\n\n /**\n * Handles double clicks on the map\n * @param {*} event\n */\n function dblclickHandler(event) {\n hideContextMenu();\n hideAdvancedSettings();\n unselectAll();\n if (event.target.classList.contains('learningmap-mapcontainer') ||\n event.target.classList.contains('learningmap-background-image')) {\n addPlace(event);\n } else if (event.target.classList.contains('learningmap-place')) {\n if (lastTarget == event.target.id) {\n lastTarget = null;\n clickHandler(event);\n } else {\n removePlace(event);\n }\n } else if (event.target.classList.contains('learningmap-path')) {\n removePath(event.target.id);\n }\n updateCode();\n }\n\n /**\n * Returns an empty title tag with the given id.\n * @param {*} id id for the title\n * @returns {any}\n */\n function title(id) {\n let title = document.createElementNS('http://www.w3.org/2000/svg', 'title');\n title.setAttribute('id', id);\n return title;\n }\n\n /**\n * Returns an text tag with the given id.\n * @param {*} id id for the text\n * @param {*} content content of the tag\n * @param {*} x x coordinate of the text\n * @param {*} y y coordinate of the text\n * @returns {any}\n */\n function text(id, content, x, y) {\n let text = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n text.setAttribute('id', id);\n text.setAttribute('x', x);\n text.setAttribute('y', y);\n // Default value for delta: Circle radius * 1.5 (as a padding)\n text.setAttribute('dx', circleRadius * 1.5);\n text.setAttribute('dy', circleRadius * 1.5);\n let textcontent = svgdoc.createCDATASection(content);\n text.replaceChildren(textcontent);\n return text;\n }\n\n /**\n * Returns a circle tag with the given dimensions.\n * @param {*} x x coordinate of the center\n * @param {*} y y coordinate of the center\n * @param {*} r radius\n * @param {*} classes classes to add\n * @param {*} id id of the circle\n * @returns {any}\n */\n function circle(x, y, r, classes, id) {\n let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n circle.setAttribute('class', classes);\n circle.setAttribute('id', id);\n circle.setAttribute('cx', x);\n circle.setAttribute('cy', y);\n circle.setAttribute('r', r);\n return circle;\n }\n\n /**\n * Returns a path between two points.\n * @param {*} x1 x coordinate of the first point\n * @param {*} y1 y coordinate of the first point\n * @param {*} x2 x coordinate of the second point\n * @param {*} y2 y coordinate of the second point\n * @param {*} classes CSS classes to set\n * @param {*} id id of the path\n * @returns {any}\n */\n function path(x1, y1, x2, y2, classes, id) {\n let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n path.setAttribute('class', classes);\n path.setAttribute('id', id);\n path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2);\n return path;\n }\n\n /**\n * Returns a link around a given child element. This function also adds a title element next\n * to the child for accessibility.\n * @param {*} child child item to set the link on\n * @param {*} id id of the link\n * @param {*} title title of the link\n * @param {*} text text to describe the link\n * @returns {any}\n */\n function link(child, id, title = null, text = null) {\n let link = document.createElementNS('http://www.w3.org/2000/svg', 'a');\n link.setAttribute('id', id);\n link.setAttribute('xlink:href', '');\n link.setAttribute('tabindex', '0');\n link.appendChild(child);\n if (title !== null) {\n link.appendChild(title);\n }\n if (text !== null) {\n link.appendChild(text);\n }\n return link;\n }\n\n /**\n * Set the title and description of the svg document.\n * @param {*} placestore\n * @param {*} values\n */\n function setTitleAndDesc(placestore, values) {\n let titlenode = svgnode.querySelector('svg>title');\n if (!titlenode) {\n titlenode = document.createElementNS('http://www.w3.org/2000/svg', 'title');\n svgnode.appendChild(titlenode);\n }\n let titlecontent = svgdoc.createCDATASection(values.title);\n titlenode.replaceChildren(titlecontent);\n titlenode.setAttribute('id', 'title-' + placestore.getMapid());\n\n let descnode = svgnode.querySelector('svg>desc');\n if (!descnode) {\n descnode = document.createElementNS('http://www.w3.org/2000/svg', 'desc');\n svgnode.appendChild(descnode);\n }\n let desccontent = svgdoc.createCDATASection(values.description);\n descnode.replaceChildren(desccontent);\n descnode.setAttribute('id', 'desc-' + placestore.getMapid());\n }\n\n /**\n * Get title and description of the svg document.\n * @returns object\n */\n function getTitleAndDesc() {\n let title = '';\n let desc = '';\n let titlenode = svgnode.querySelector('svg>title');\n if (titlenode) {\n title = titlenode.textContent;\n }\n let descnode = svgnode.querySelector('svg>desc');\n if (descnode) {\n desc = descnode.textContent;\n }\n return {\n title: title,\n description: desc\n };\n }\n\n /**\n * Adds a place on the SVG map. This function also prepares the code for linking activities\n * and adding titles (for accessibility).\n * @param {*} event event causing the command\n */\n function addPlace(event) {\n let placesgroup = document.getElementById('placesGroup');\n let placeId = 'p' + placestore.getId();\n let linkId = 'a' + placestore.getId();\n var CTM = event.target.getScreenCTM();\n if (event.touches) {\n event = event.touches[0];\n }\n let cx = (event.clientX - CTM.e) / CTM.a;\n let cy = (event.clientY - CTM.f) / CTM.d;\n placesgroup.appendChild(\n link(\n circle(cx, cy, circleRadius, 'learningmap-place learningmap-draggable learningmap-emptyplace', placeId),\n linkId,\n title('title' + placeId),\n text('text' + placeId, '', cx, cy)\n )\n );\n placestore.addPlace(placeId, linkId);\n }\n\n /**\n * Handles single clicks on the background image.\n * @param {*} event click event\n * @returns {void}\n */\n function clickHandler(event) {\n event.preventDefault();\n hideContextMenu();\n hideAdvancedSettings();\n if (event.target.classList.contains('learningmap-place') && selectedElement === null) {\n if (firstPlace === null) {\n firstPlace = event.target.id;\n document.getElementById(firstPlace).classList.add('learningmap-selected');\n } else {\n secondPlace = event.target.id;\n let fid = parseInt(firstPlace.replace('p', ''));\n let sid = parseInt(secondPlace.replace('p', ''));\n if (sid == fid) {\n return;\n }\n if (sid < fid) {\n let z = sid;\n sid = fid;\n fid = z;\n }\n addPath(fid, sid);\n let first = document.getElementById(firstPlace);\n if (first) {\n first.classList.remove('learningmap-selected');\n }\n firstPlace = null;\n lastTarget = secondPlace;\n secondPlace = null;\n }\n } else {\n unselectAll();\n firstPlace = null;\n }\n }\n /**\n * Removes the classes 'learningmap-selected' and 'learningmap-selectet-activity-selector'\n * from all nodes\n */\n function unselectAll() {\n Array.from(document.getElementsByClassName('learningmap-selected')).forEach(function(e) {\n e.classList.remove('learningmap-selected');\n });\n Array.from(document.getElementsByClassName('learningmap-selected-activity-selector')).forEach(function(e) {\n e.classList.remove('learningmap-selected-activity-selector');\n });\n }\n\n /**\n * Adds a path between two places.\n * @param {number} fid id of the first place (meant to be the smaller one)\n * @param {number} sid id of the second place (meant to be the bigger one)\n */\n function addPath(fid, sid) {\n let pid = 'p' + fid + '_' + sid;\n if (document.getElementById(pid) === null) {\n let pathsgroup = document.getElementById('pathsGroup');\n let first = document.getElementById('p' + fid);\n let second = document.getElementById('p' + sid);\n if (pathsgroup && first && second) {\n pathsgroup.appendChild(\n path(\n first.cx.baseVal.value,\n first.cy.baseVal.value,\n second.cx.baseVal.value,\n second.cy.baseVal.value,\n 'learningmap-path',\n pid\n )\n );\n placestore.addPath(pid, 'p' + fid, 'p' + sid);\n }\n }\n }\n\n /**\n * Removes a place from the SVG and the placestore. This function also removes all\n * touching paths and entries in startingplaces / targetplaces linking to the removed\n * place.\n * @param {any} event event causing the remove order\n */\n function removePlace(event) {\n let place = document.getElementById(event.target.id);\n let parent = place.parentNode;\n removePathsTouchingPlace(event.target.id);\n placestore.removePlace(event.target.id);\n parent.removeChild(place);\n parent.parentNode.removeChild(parent);\n\n updateCode();\n }\n\n /**\n * Removes all paths touching a certain place\n * @param {number} id id of the place\n */\n function removePathsTouchingPlace(id) {\n placestore.getTouchingPaths(id).forEach(\n function(e) {\n removePath(e.id);\n }\n );\n }\n\n /**\n * Removes a path from the SVG and from the placestore\n * @param {number} id id of the path\n */\n function removePath(id) {\n let path = document.getElementById(id);\n if (path !== null) {\n path.parentNode.removeChild(path);\n placestore.removePath(id);\n }\n }\n\n /**\n * Sets the background image of the SVG to the current image in filemanager.\n */\n function refreshBackgroundImage() {\n let previewimage = document.getElementsByClassName('realpreview');\n if (previewimage.length > 0) {\n let background = document.getElementById('learningmap-background-image');\n let backgroundurl = previewimage[0].getAttribute('src').split('?')[0];\n // If the uploaded file reuses the filename of a previously uploaded image, they differ\n // only in the oid. So one has to append the oid to the url.\n if (previewimage[0].getAttribute('src').split('?')[1].includes('&oid=')) {\n backgroundurl += '?oid=' + previewimage[0].getAttribute('src').split('&oid=')[1];\n }\n background.setAttribute('xlink:href', backgroundurl);\n }\n }\n\n /**\n * Adds an eventListener to the background image for watching file changes and updating\n * height and width of the image.\n */\n function registerBackgroundListener() {\n let background = document.getElementById('learningmap-background-image');\n if (background) {\n background.addEventListener('load', function() {\n background.removeAttribute('height');\n let height = parseInt(background.getBBox().height);\n let width = background.getBBox().width;\n placestore.setBackgroundDimensions(width, height);\n svgnode.setAttribute('viewBox', '0 0 ' + placestore.width + ' ' + placestore.height);\n background.setAttribute('width', width);\n background.setAttribute('height', height);\n updateCode();\n });\n }\n }\n\n /**\n * Updates CSS code inside the SVG (called, when one of the colors is changed).\n * Calls updateCode() when completed.\n */\n function updateCSS() {\n Templates.renderForPromise('mod_learningmap/cssskeleton', placestore.getPlacestore())\n .then(({html, js}) => {\n Templates.replaceNode('#learningmap-svgstyle', html, js);\n updateCode();\n return true;\n })\n .catch(ex => displayException(ex));\n }\n\n /**\n * Updates the activity selector to highlight the activities already used\n * and to show the alert for hidden activities.\n */\n function updateActivities() {\n let activities = placestore.getAllActivities();\n let options = Array.from(activitySelector.getElementsByTagName('option'));\n activityHiddenWarning.setAttribute('hidden', '');\n options.forEach(function(n) {\n if (activities.includes(n.value)) {\n n.classList.add('learningmap-used-activity');\n if (n.selected) {\n if (n.getAttribute('data-activity-hidden') == true) {\n activityHiddenWarning.removeAttribute('hidden');\n }\n }\n } else {\n n.classList.remove('learningmap-used-activity');\n }\n });\n }\n\n /**\n * Adds the event listener to the color chooser buttons.\n * @param {*} name name of the color\n * @param {*} secondValue name of a second placestore value that has to be changed along\n */\n function colorChooserLogic(name, secondValue = '') {\n let colorChooser = document.getElementById('learningmap-color-' + name);\n if (colorChooser) {\n colorChooser.addEventListener('change', function() {\n placestore.setColor(name, colorChooser.value);\n if (secondValue != '') {\n placestore.setColor(secondValue, colorChooser.value);\n }\n updateCSS();\n });\n colorChooser.value = placestore.getColor(name);\n }\n }\n\n /**\n * Adds the event listener to advanced settings menu items\n * @param {*} name Name of the item\n * @param {*} getCall Method of placestore to call to read value\n * @param {*} setCall Method of placestore to call to save value\n * @param {*} callback Additional callback after value is saved\n */\n async function advancedSettingsLogic(name, getCall, setCall, callback = null) {\n let settingItem = document.getElementById('learningmap-advanced-setting-' + name);\n if (settingItem) {\n // Settings that need a modal for editing (e.g. title and description) have a link to\n // open the modal, other settings have a checkbox.\n if (settingItem.nodeName == 'A') {\n const descriptionHandler = async() => {\n const values = getCall();\n try {\n const strings = await Str.get_strings([\n {key: 'titleanddescription', component: 'mod_learningmap'},\n {key: 'save', component: 'core'},\n ]);\n return saveCancel(\n strings[0],\n Templates.render('mod_learningmap/' + name + '-modal', values),\n strings[1],\n () => {\n let values = {};\n let formentries = document.querySelectorAll('.mod_learningmap_' + name + '_value');\n formentries.forEach((element) => {\n values[element.name] = element.value;\n });\n setCall(placestore, values);\n if (callback !== null) {\n callback();\n }\n updateCSS();\n }\n );\n } catch (ex) {\n displayException(ex);\n return null;\n }\n };\n settingItem.addEventListener('click', descriptionHandler);\n settingItem.addEventListener('keypress', (e) => {\n if (e.key === 'Enter') {\n descriptionHandler();\n }\n });\n // Boolean settings can be directly changed with a checkbox in the advanced settings menu.\n } else {\n settingItem.checked = getCall.call(placestore);\n settingItem.addEventListener('change', function() {\n setCall.call(placestore, settingItem.checked);\n if (callback !== null) {\n callback();\n }\n updateCSS();\n });\n }\n }\n }\n\n /**\n * Adds missing text nodes\n */\n function fixPlaceLabels() {\n let options = Array.from(activitySelector.getElementsByTagName('option'));\n let places = placestore.getPlaces();\n for (const place of places) {\n if (document.getElementById('text' + place.id) === null) {\n let content = '';\n for (const option of options) {\n if (option.value == place.linkedActivity) {\n content = option.textContent;\n break;\n }\n }\n let placeNode = document.getElementById(place.id);\n let textNode = text('text' + place.id, content, placeNode.cx.baseVal.value, placeNode.cy.baseVal.value);\n placeNode.parentNode.appendChild(textNode);\n }\n }\n }\n\n /**\n * Hides the advanced settings menu.\n */\n function hideAdvancedSettings() {\n let advancedSettings = document.getElementById('learningmap-advanced-settings');\n advancedSettings.setAttribute('hidden', '');\n }\n};\n"],"names":["targetPoints","pathTypes","async","offset","dragel","pathsToUpdateFirstPoint","pathsToUpdateSecondPoint","prefetchTemplates","selectedElement","firstPlace","secondPlace","lastTarget","elementForActivitySelector","touchstart","touchend","touchmove","mapdiv","document","getElementById","code","svgdoc","DOMParser","parseFromString","value","svgnode","querySelector","activitySetting","activitySelector","activityStarting","activityTarget","activityHiddenWarning","advancedSettingsIcon","treeView","setAttribute","iconView","setTimeout","dispatchEvent","Event","addEventListener","setActivityId","text","replaceChildren","createCDATASection","textContent","title","classList","remove","add","updateActivities","updateCode","checked","addStartingPlace","removeStartingPlace","addTargetPlace","removeTargetPlace","placestoreInput","getElementsByName","loadJSON","advancedSettings","getAttribute","hideAdvancedSettings","removeAttribute","hideContextMenu","e","key","preventDefault","click","advancedSettingsClose","advancedSettingsLogic","placestore","getHidePaths","setHidePaths","getUseCheckmark","setUseCheckmark","getHover","setHover","getPulse","setPulse","getShowall","setShowall","getHideStroke","setHideStroke","getShowText","setShowText","options","Array","from","getElementsByTagName","places","getPlaces","place","id","content","option","linkedActivity","placeNode","textNode","cx","baseVal","cy","parentNode","appendChild","getSliceMode","setSliceMode","getShowWayGone","setShowWayGone","desc","titlenode","descnode","description","values","createElementNS","titlecontent","getMapid","desccontent","showContextMenu","unselectAll","target","touches","contains","activityId","getActivityId","scalingFactor","clientWidth","style","setProperty","clientHeight","display","isStartingPlace","isTargetPlace","colorChooserLogic","refreshBackgroundImage","background","height","parseInt","getBBox","width","setBackgroundDimensions","registerBackgroundListener","el","startDrag","drag","endDrag","evt","cancelable","nodeName","dblclickHandler","endTouch","getMousePosition","x","getAttributeNS","y","getPathsWithFid","getPathsWithSid","pathPoint","transformCoordinates","layerX","layerY","coord","dx","dy","setAttributeNS","updatePathDeclaration","forEach","path","pathNode","clickHandler","oldDefinition","targetX","targetY","targetP","parts","split","fromX","fromY","toX","toY","bezierX","bezierY","pathType","i","length","makeDraggable","updateCSS","backgroundfileNode","MutationObserver","observe","attributes","childList","subtree","clientX","clientY","CTM","getScreenCTM","a","f","d","innerHTML","JSON","stringify","getPlacestore","event","placesgroup","placeId","getId","linkId","child","link","r","classes","circle","addPlace","parent","getTouchingPaths","removePath","removePlace","removeChild","circleRadius","textcontent","fid","replace","sid","z","pid","pathsgroup","first","second","x1","y1","x2","y2","addPath","getElementsByClassName","previewimage","backgroundurl","includes","renderForPromise","then","_ref","html","js","replaceNode","catch","ex","activities","getAllActivities","n","selected","name","secondValue","colorChooser","setColor","getColor","getCall","setCall","callback","settingItem","descriptionHandler","strings","Str","get_strings","component","Templates","render","querySelectorAll","element","call"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"learningmap.min.js","sources":["../src/learningmap.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main module for the learningmap editor\n *\n * @module mod_learningmap/learningmap\n * @copyright 2021-2026 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {exception as displayException, saveCancel} from 'core/notification';\nimport Templates from 'core/templates';\nimport placestore from 'mod_learningmap/placestore';\nimport * as Str from 'core/str';\nimport {debounce} from 'core/utils';\n\nconst circleRadius = 10;\n\n// Constants for updatePathDeclaration.\nconst targetPoints = {\n firstPoint: 1,\n secondPoint: 2,\n bezierPoint: 3,\n};\n\nconst pathTypes = {\n line: 1,\n quadraticbezier: 2,\n};\n\nexport const init = async() => {\n // Load the needed template on startup for better execution speed.\n Templates.prefetchTemplates(['mod_learningmap/cssskeleton']);\n\n // Variable for storing the mouse offset\n var offset;\n\n // Variable for draggable element\n var dragel;\n\n // Variables for storing the paths that need update of the first or\n // the second coordinates.\n var pathsToUpdateFirstPoint, pathsToUpdateSecondPoint;\n\n // Variables for handling the currently selected elements\n var selectedElement = null,\n firstPlace = null,\n secondPlace = null,\n lastTarget = null;\n\n // Variable for storing the selected element for the activity selector\n var elementForActivitySelector = null;\n\n // Variables for simulating double click on touch devices, set when the\n // corresponding events are handled\n var touchstart = false;\n var touchend = false;\n // Counter for touchmove events\n var touchmove = 0;\n\n // DOM nodes for the editor\n let mapdiv = document.getElementById('learningmap-editor-map');\n let code = document.getElementById('id_svgcode');\n\n let svgdoc = new DOMParser().parseFromString(code.value, 'image/svg+xml');\n let svgnode = svgdoc.querySelector('svg');\n\n // DOM nodes for the activity selector\n let activitySetting = document.getElementById('learningmap-activity-setting');\n let activitySelector = document.getElementById('learningmap-activity-selector');\n let activityStarting = document.getElementById('learningmap-activity-starting');\n let activityTarget = document.getElementById('learningmap-activity-target');\n let activityHiddenWarning = document.getElementById('learningmap-activity-hidden-warning');\n let advancedSettingsIcon = document.getElementById('learningmap-advanced-settings-icon');\n\n // Hide tree view as there is no preview file we can attach to\n let treeView = document.querySelector('.fp-viewbar .fp-vb-tree');\n if (treeView) {\n treeView.setAttribute('style', 'display: none;');\n }\n\n // Trigger click event on icon view to ensure that tree view is not active.\n let iconView = document.querySelector('.fp-viewbar .fp-vb-icons');\n if (iconView) {\n // Handle possible delay in form loading.\n setTimeout(() => {\n iconView.dispatchEvent(new Event('click'));\n }, 1000);\n }\n\n // Attach listeners to the activity selector\n if (activitySelector) {\n // Show places that are not linked to an activity\n activitySelector.addEventListener('change', function() {\n placestore.setActivityId(elementForActivitySelector, activitySelector.value);\n if (activitySelector.value) {\n let text = document.getElementById('text' + elementForActivitySelector);\n if (text) {\n text.replaceChildren(svgdoc.createTextNode(\n activitySelector.querySelector('option[value=\"' + activitySelector.value + '\"]').textContent\n ));\n }\n let title = document.getElementById('title' + elementForActivitySelector);\n if (title) {\n title.replaceChildren(svgdoc.createTextNode(\n activitySelector.querySelector('option[value=\"' + activitySelector.value + '\"]').textContent\n ));\n }\n document.getElementById(elementForActivitySelector).classList.remove('learningmap-emptyplace');\n } else {\n document.getElementById(elementForActivitySelector).classList.add('learningmap-emptyplace');\n }\n updateActivities();\n updateCode();\n });\n // Add / remove a place to the starting places array\n activityStarting.addEventListener('change', function() {\n if (activityStarting.checked) {\n placestore.addStartingPlace(elementForActivitySelector);\n } else {\n placestore.removeStartingPlace(elementForActivitySelector);\n }\n updateCode();\n });\n // Add / remove a place to the target places array\n activityTarget.addEventListener('change', function() {\n if (activityTarget.checked) {\n placestore.addTargetPlace(elementForActivitySelector);\n document.getElementById(elementForActivitySelector).classList.add('learningmap-targetplace');\n } else {\n placestore.removeTargetPlace(elementForActivitySelector);\n document.getElementById(elementForActivitySelector).classList.remove('learningmap-targetplace');\n }\n updateCode();\n });\n }\n\n // Load placestore values from the hidden input field\n let placestoreInput = document.getElementsByName('placestore')[0];\n if (placestoreInput) {\n placestore.loadJSON(placestoreInput.value);\n }\n\n // Mark all activities in the placestore as \"used\".\n updateActivities();\n\n // Attach listeners to the advanced settings div\n if (advancedSettingsIcon) {\n let advancedSettings = document.getElementById('learningmap-advanced-settings');\n advancedSettingsIcon.addEventListener('click', function() {\n if (advancedSettings.getAttribute('hidden') === null) {\n hideAdvancedSettings();\n } else {\n advancedSettings.removeAttribute('hidden');\n hideContextMenu();\n }\n });\n advancedSettingsIcon.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n advancedSettingsIcon.click();\n }\n });\n let advancedSettingsClose = document.getElementById('learningmap-advanced-settings-close');\n if (advancedSettingsClose) {\n advancedSettingsClose.addEventListener('click', function() {\n advancedSettings.setAttribute('hidden', '');\n });\n }\n\n await advancedSettingsLogic('hidepaths', placestore.getHidePaths, placestore.setHidePaths);\n await advancedSettingsLogic('usecheckmark', placestore.getUseCheckmark, placestore.setUseCheckmark);\n await advancedSettingsLogic('hover', placestore.getHover, placestore.setHover);\n await advancedSettingsLogic('pulse', placestore.getPulse, placestore.setPulse);\n await advancedSettingsLogic('showall', placestore.getShowall, placestore.setShowall);\n await advancedSettingsLogic('hidestroke', placestore.getHideStroke, placestore.setHideStroke);\n await advancedSettingsLogic('showtext', placestore.getShowText, placestore.setShowText, fixPlaceLabels);\n await advancedSettingsLogic('slicemode', placestore.getSliceMode, placestore.setSliceMode);\n await advancedSettingsLogic('showwaygone', placestore.getShowWayGone, placestore.setShowWayGone);\n await advancedSettingsLogic('description', getTitleAndDesc, setTitleAndDesc);\n }\n\n // Attach listener to the color choosers\n colorChooserLogic('stroke', 'text');\n colorChooserLogic('place');\n colorChooserLogic('visited');\n\n // Get SVG code from the (hidden) textarea field\n if (code && mapdiv) {\n mapdiv.replaceChildren(svgnode);\n }\n // Reload background image to get the correct width and height values\n refreshBackgroundImage();\n registerBackgroundListener();\n updateCode();\n\n // Enable dragging of places\n makeDraggable(svgnode);\n\n // Refresh stylesheet values from placestore\n updateCSS();\n\n // Add listeners for clicking and context menu\n if (mapdiv) {\n mapdiv.addEventListener('dblclick', dblclickHandler);\n mapdiv.addEventListener('click', clickHandler);\n\n mapdiv.addEventListener('contextmenu', function(e) {\n e.preventDefault();\n showContextMenu(e);\n }, false);\n }\n /**\n * Shows the context menu at the current mouse position\n * @param {*} e\n */\n function showContextMenu(e) {\n unselectAll();\n hideAdvancedSettings();\n // Check for the existence of the target (could have vanished since the event started).\n if (activitySetting && document.getElementById(e.target.id) !== null) {\n if (e.touches) {\n e = e.touches[0];\n }\n if (e.target.classList.contains('learningmap-place')) {\n e.target.classList.add('learningmap-selected-activity-selector');\n let activityId = placestore.getActivityId(e.target.id);\n let scalingFactor = mapdiv.clientWidth / 800;\n activitySetting.style.setProperty('--pos-x', e.target.cx.baseVal.value * scalingFactor + 'px');\n activitySetting.style.setProperty('--pos-y', e.target.cy.baseVal.value * scalingFactor + 'px');\n activitySetting.style.setProperty('--map-width', mapdiv.clientWidth + 'px');\n activitySetting.style.setProperty('--map-height', mapdiv.clientHeight + 'px');\n activitySetting.style.display = 'block';\n document.getElementById('learningmap-activity-selector').value = activityId;\n document.getElementById('learningmap-activity-starting').checked = placestore.isStartingPlace(e.target.id);\n document.getElementById('learningmap-activity-target').checked = placestore.isTargetPlace(e.target.id);\n elementForActivitySelector = e.target.id;\n updateActivities();\n } else {\n hideContextMenu();\n hideAdvancedSettings();\n }\n }\n }\n\n /**\n * Hides the context menu\n */\n function hideContextMenu() {\n let e = document.getElementById(elementForActivitySelector);\n if (e) {\n e.classList.remove('learningmap-selected-activity-selector');\n }\n activitySetting.style.display = 'none';\n }\n\n let backgroundfileNode = document.getElementById('id_backgroundfile_fieldset');\n if (backgroundfileNode) {\n let observer = new MutationObserver(refreshBackgroundImage);\n observer.observe(backgroundfileNode, {attributes: true, childList: true, subtree: true});\n }\n\n /**\n * Helper function for getting the right coordinates from the mouse\n * @param {*} evt\n * @returns {object}\n */\n function getMousePosition(evt) {\n if (evt.touches) {\n evt = evt.touches[0];\n }\n return transformCoordinates(evt.clientX, evt.clientY);\n }\n\n /**\n * Transforms client coordinates to SVG coordinates\n * @param {number} x x coordinate to transform\n * @param {number} y y coordinate to transform\n * @returns {object} Object containing transformed x and y coordinate\n */\n function transformCoordinates(x, y) {\n var CTM = dragel.getScreenCTM();\n return {\n x: (x - CTM.e) / CTM.a,\n y: (y - CTM.f) / CTM.d\n };\n }\n\n /**\n * Enables dragging on an DOM node\n * @param {*} el\n */\n function makeDraggable(el) {\n dragel = el;\n if (el) {\n el.addEventListener('mousedown', startDrag);\n el.addEventListener('mousemove', debounce(drag, 5));\n el.addEventListener('mouseup', endDrag);\n el.addEventListener('mouseleave', endDrag);\n el.addEventListener('touchstart', startTouch);\n el.addEventListener('touchmove', debounce(drag, 5));\n el.addEventListener('touchend', endTouch);\n el.addEventListener('touchleave', endTouch);\n el.addEventListener('touchcancel', endTouch);\n }\n\n /**\n * Function called whenn dragging starts.\n * @param {*} evt\n */\n function startDrag(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n pathsToUpdateFirstPoint = [];\n pathsToUpdateSecondPoint = [];\n if (evt.target.classList.contains('learningmap-draggable')) {\n selectedElement = evt.target;\n offset = getMousePosition(evt);\n offset.x -= parseInt(selectedElement.getAttributeNS(null, \"cx\"));\n offset.y -= parseInt(selectedElement.getAttributeNS(null, \"cy\"));\n // Get paths that need to be updated.\n pathsToUpdateFirstPoint = placestore.getPathsWithFid(selectedElement.id);\n pathsToUpdateSecondPoint = placestore.getPathsWithSid(selectedElement.id);\n } else if (evt.target.nodeName == 'text') {\n selectedElement = evt.target;\n let place = findPlaceForText(selectedElement.id);\n offset = getMousePosition(evt);\n offset.x -= parseInt(selectedElement.getAttributeNS(null, \"dx\")) + place.cx.baseVal.value;\n offset.y -= parseInt(selectedElement.getAttributeNS(null, \"dy\")) + place.cy.baseVal.value;\n } else if (evt.target.nodeName == 'path') {\n selectedElement = evt.target;\n offset = getMousePosition(evt);\n let pathPoint = transformCoordinates(evt.layerX, evt.layerY);\n offset.x += pathPoint.x;\n offset.y += pathPoint.y;\n }\n }\n\n /**\n * Function called during dragging. Continuously updates circles center coordinates and the\n * coordinates of the touching paths.\n * @param {*} evt\n */\n function drag(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n // Count touchmove events\n touchmove++;\n if (selectedElement) {\n var coord = getMousePosition(evt);\n let cx = coord.x - offset.x;\n let cy = coord.y - offset.y;\n if (selectedElement.nodeName == 'text') {\n let place = findPlaceForText(selectedElement.id);\n // Calculate the delta from the current mouse position to the corresponding place.\n // coord: current mouse position\n // offset: delta from the mouse position to the coordinates of the text node\n let dx = coord.x - offset.x - place.cx.baseVal.value;\n let dy = coord.y - offset.y - place.cy.baseVal.value;\n selectedElement.setAttributeNS(null, \"dx\", dx);\n selectedElement.setAttributeNS(null, \"dy\", dy);\n }\n if (selectedElement.nodeName == 'path') {\n selectedElement.setAttribute(\n 'd',\n updatePathDeclaration(selectedElement.getAttribute('d'), coord.x, coord.y, targetPoints.bezierPoint)\n );\n }\n if (selectedElement.nodeName == 'circle') {\n selectedElement.setAttributeNS(null, \"cx\", cx);\n selectedElement.setAttributeNS(null, \"cy\", cy);\n let textNode = document.getElementById('text' + selectedElement.id);\n if (textNode !== null) {\n textNode.setAttributeNS(null, 'x', cx);\n textNode.setAttributeNS(null, 'y', cy);\n }\n pathsToUpdateFirstPoint.forEach(function(path) {\n let pathNode = document.getElementById(path.id);\n if (pathNode !== null) {\n if (pathNode.nodeName == 'path') {\n pathNode.setAttribute(\n 'd',\n updatePathDeclaration(pathNode.getAttribute('d'), cx, cy, targetPoints.firstPoint)\n );\n } else {\n pathNode.setAttribute('x1', cx);\n pathNode.setAttribute('y1', cy);\n }\n }\n });\n\n pathsToUpdateSecondPoint.forEach(function(path) {\n let pathNode = document.getElementById(path.id);\n if (pathNode !== null) {\n if (pathNode.nodeName == 'path') {\n pathNode.setAttribute(\n 'd',\n updatePathDeclaration(pathNode.getAttribute('d'), cx, cy, targetPoints.secondPoint)\n );\n } else {\n pathNode.setAttribute('x2', cx);\n pathNode.setAttribute('y2', cy);\n }\n }\n });\n }\n }\n }\n\n /**\n * Function called when dragging ends.\n * @param {*} evt\n */\n function endDrag(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n selectedElement = null;\n unselectAll();\n updateCode();\n }\n\n /**\n * Function called when touchstart event occurs.\n * @param {*} evt\n */\n function startTouch(evt) {\n if (evt.cancelable) {\n evt.preventDefault();\n }\n if (\n evt.target.classList.contains('learningmap-draggable') ||\n evt.target.nodeName == 'text' ||\n evt.target.nodeName == 'path'\n ) {\n if (!touchstart) {\n touchstart = true;\n touchmove = 0;\n touchend = false;\n setTimeout(\n (evt) => {\n if (touchmove < 3 && !touchend) {\n if (evt.touches) {\n evt = evt.touches[0];\n }\n showContextMenu(evt);\n }\n },\n 2000,\n evt\n );\n setTimeout(\n () => {\n touchstart = false;\n },\n 300);\n } else {\n dblclickHandler(evt);\n touchstart = false;\n }\n startDrag(evt);\n } else {\n if (!touchstart) {\n touchstart = true;\n touchend = false;\n touchmove = 0;\n setTimeout(\n () => {\n touchstart = false;\n },\n 300);\n } else {\n dblclickHandler(evt);\n touchstart = false;\n }\n }\n }\n\n /**\n * Function called when touchend, touchleave or touchcancel event occurs.\n * @param {*} evt\n */\n function endTouch(evt) {\n selectedElement = null;\n touchend = true;\n // If there was only a small move (<3 move events), this also counts as a click.\n if (touchmove < 3 && touchstart) {\n clickHandler(evt);\n } else {\n endDrag(evt);\n }\n if (evt.cancelable) {\n evt.preventDefault();\n }\n }\n\n /**\n * Updates the path declaration of lines and quadratic bezier curves setting one of the points.\n * @param {string} oldDefinition SVG path definition string\n * @param {number} targetX x coordinate of the point to set\n * @param {number} targetY y coordinate of the point to set\n * @param {number} targetP Which point to change (you can use the targetPoints constants here)\n * @returns {string} Updated SVG path definition\n */\n function updatePathDeclaration(oldDefinition, targetX, targetY, targetP = targetPoints.firstPoint) {\n let parts = oldDefinition.split(' ');\n let fromX = 0;\n let fromY = 0;\n let toX = 0;\n let toY = 0;\n let bezierX = 0;\n let bezierY = 0;\n let pathType = pathTypes.line;\n\n // The d attribute of an SVG path in a learning map can have two different formats (in this version):\n // \"M x1 y1 L x2 y2\" Line from x1, y1 to x2, y2\n // \"M x1 y2 Q x3 y3 x2 y2\" Quadratic bezier curve inside the triangle defined by x1, y1, x2, y2 and x3, y3.\n for (let i = 0; i < parts.length; i++) {\n // Every path contains the first point in that way.\n if (parts[i] == 'M') {\n fromX = parseInt(parts[i + 1]);\n fromY = parseInt(parts[i + 2]);\n i += 2;\n }\n // This path is a direct line, so there are only two points in total.\n if (parts[i] == 'L') {\n toX = parseInt(parts[i + 1]);\n toY = parseInt(parts[i + 2]);\n i += 2;\n }\n // This path is a bezier curve, there are three points in total.\n if (parts[i] == 'Q') {\n bezierX = parseInt(parts[i + 1]);\n bezierY = parseInt(parts[i + 2]);\n toX = parseInt(parts[i + 3]);\n toY = parseInt(parts[i + 4]);\n i += 4;\n pathType = pathTypes.quadraticbezier;\n }\n }\n\n switch (targetP) {\n case targetPoints.firstPoint:\n fromX = targetX;\n fromY = targetY;\n break;\n case targetPoints.secondPoint:\n toX = targetX;\n toY = targetY;\n break;\n case targetPoints.bezierPoint:\n // Calculate the third triangle point for the bezier curve.\n bezierX = targetX * 2 - (fromX + toX) * 0.5;\n bezierY = targetY * 2 - (fromY + toY) * 0.5;\n pathType = pathTypes.quadraticbezier;\n break;\n }\n\n if (pathType == pathTypes.quadraticbezier) {\n return 'M ' + fromX + ' ' + fromY + ' Q ' + bezierX + ' ' + bezierY + ', ' + toX + ' ' + toY;\n } else {\n return 'M ' + fromX + ' ' + fromY + ' L ' + toX + ' ' + toY;\n }\n }\n }\n\n /**\n * Updates the form fields for the SVG code and the placestore from the editor.\n */\n function updateCode() {\n if (code && mapdiv) {\n code.innerHTML = mapdiv.innerHTML;\n }\n if (placestoreInput) {\n document.getElementsByName('placestore')[0].value = JSON.stringify(placestore.getPlacestore());\n }\n }\n\n /**\n * Handles double clicks on the map\n * @param {*} event\n */\n function dblclickHandler(event) {\n hideContextMenu();\n hideAdvancedSettings();\n unselectAll();\n if (event.target.classList.contains('learningmap-mapcontainer') ||\n event.target.classList.contains('learningmap-background-image')) {\n addPlace(event);\n } else if (event.target.classList.contains('learningmap-place')) {\n if (lastTarget == event.target.id) {\n lastTarget = null;\n clickHandler(event);\n } else {\n removePlace(event);\n }\n } else if (event.target.classList.contains('learningmap-path')) {\n removePath(event.target.id);\n }\n updateCode();\n }\n\n /**\n * Returns an empty title tag with the given id.\n * @param {*} id id for the title\n * @returns {any}\n */\n function title(id) {\n let title = document.createElementNS('http://www.w3.org/2000/svg', 'title');\n title.setAttribute('id', id);\n return title;\n }\n\n /**\n * Returns an text tag with the given id.\n * @param {*} id id for the text\n * @param {*} content content of the tag\n * @param {*} x x coordinate of the text\n * @param {*} y y coordinate of the text\n * @returns {any}\n */\n function text(id, content, x, y) {\n let text = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n text.setAttribute('id', id);\n text.setAttribute('x', x);\n text.setAttribute('y', y);\n // Default value for delta: Circle radius * 1.5 (as a padding)\n text.setAttribute('dx', circleRadius * 1.5);\n text.setAttribute('dy', circleRadius * 1.5);\n let textcontent = svgdoc.createTextNode(content);\n text.replaceChildren(textcontent);\n return text;\n }\n\n /**\n * Returns a circle tag with the given dimensions.\n * @param {*} x x coordinate of the center\n * @param {*} y y coordinate of the center\n * @param {*} r radius\n * @param {*} classes classes to add\n * @param {*} id id of the circle\n * @returns {any}\n */\n function circle(x, y, r, classes, id) {\n let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n circle.setAttribute('class', classes);\n circle.setAttribute('id', id);\n circle.setAttribute('cx', x);\n circle.setAttribute('cy', y);\n circle.setAttribute('r', r);\n return circle;\n }\n\n /**\n * Returns a path between two points.\n * @param {*} x1 x coordinate of the first point\n * @param {*} y1 y coordinate of the first point\n * @param {*} x2 x coordinate of the second point\n * @param {*} y2 y coordinate of the second point\n * @param {*} classes CSS classes to set\n * @param {*} id id of the path\n * @returns {any}\n */\n function path(x1, y1, x2, y2, classes, id) {\n let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n path.setAttribute('class', classes);\n path.setAttribute('id', id);\n path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2);\n return path;\n }\n\n /**\n * Returns a link around a given child element. This function also adds a title element next\n * to the child for accessibility.\n * @param {*} child child item to set the link on\n * @param {*} id id of the link\n * @param {*} title title of the link\n * @returns {any}\n */\n function link(child, id, title = null) {\n let link = document.createElementNS('http://www.w3.org/2000/svg', 'a');\n link.setAttribute('id', id);\n link.setAttribute('xlink:href', '');\n link.setAttribute('tabindex', '0');\n link.appendChild(child);\n if (title !== null) {\n link.appendChild(title);\n }\n return link;\n }\n\n /**\n * Set the title and description of the svg document.\n * @param {*} placestore\n * @param {*} values\n */\n function setTitleAndDesc(placestore, values) {\n let titlenode = svgnode.querySelector('svg>title');\n if (!titlenode) {\n titlenode = document.createElementNS('http://www.w3.org/2000/svg', 'title');\n svgnode.appendChild(titlenode);\n }\n let titlecontent = svgdoc.createTextNode(values.title);\n titlenode.replaceChildren(titlecontent);\n titlenode.setAttribute('id', 'title-' + placestore.getMapid());\n\n let descnode = svgnode.querySelector('svg>desc');\n if (!descnode) {\n descnode = document.createElementNS('http://www.w3.org/2000/svg', 'desc');\n svgnode.appendChild(descnode);\n }\n let desccontent = svgdoc.createTextNode(values.description);\n descnode.replaceChildren(desccontent);\n descnode.setAttribute('id', 'desc-' + placestore.getMapid());\n }\n\n /**\n * Get title and description of the svg document.\n * @returns object\n */\n function getTitleAndDesc() {\n let title = '';\n let desc = '';\n let titlenode = svgnode.querySelector('svg>title');\n if (titlenode) {\n title = titlenode.textContent;\n }\n let descnode = svgnode.querySelector('svg>desc');\n if (descnode) {\n desc = descnode.textContent;\n }\n return {\n title: title,\n description: desc\n };\n }\n\n /**\n * Adds a place on the SVG map. This function also prepares the code for linking activities\n * and adding titles (for accessibility).\n * @param {*} event event causing the command\n */\n function addPlace(event) {\n let placesgroup = document.getElementById('placesGroup');\n if (!placesgroup) {\n placesgroup = document.getElementById('placesGroup-' + placestore.getMapid());\n }\n let textgroup = document.getElementById('textsGroup-' + placestore.getMapid());\n if (!textgroup) {\n textgroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');\n textgroup.setAttribute('id', 'textsGroup-' + placestore.getMapid());\n svgnode.appendChild(textgroup);\n }\n let placeId = 'p' + placestore.getId();\n let linkId = 'a' + placestore.getId();\n var CTM = event.target.getScreenCTM();\n if (event.touches) {\n event = event.touches[0];\n }\n let cx = (event.clientX - CTM.e) / CTM.a;\n let cy = (event.clientY - CTM.f) / CTM.d;\n placesgroup.appendChild(\n link(\n circle(cx, cy, circleRadius, 'learningmap-place learningmap-draggable learningmap-emptyplace', placeId),\n linkId,\n title('title' + placeId)\n )\n );\n textgroup.appendChild(\n text('text' + placeId, '', cx, cy)\n );\n placestore.addPlace(placeId, linkId);\n }\n\n /**\n * Handles single clicks on the background image.\n * @param {*} event click event\n * @returns {void}\n */\n function clickHandler(event) {\n event.preventDefault();\n hideContextMenu();\n hideAdvancedSettings();\n if (event.target.classList.contains('learningmap-place') && selectedElement === null) {\n if (firstPlace === null) {\n firstPlace = event.target.id;\n document.getElementById(firstPlace).classList.add('learningmap-selected');\n } else {\n secondPlace = event.target.id;\n let fid = parseInt(firstPlace.replace('p', ''));\n let sid = parseInt(secondPlace.replace('p', ''));\n if (sid == fid) {\n return;\n }\n if (sid < fid) {\n let z = sid;\n sid = fid;\n fid = z;\n }\n addPath(fid, sid);\n let first = document.getElementById(firstPlace);\n if (first) {\n first.classList.remove('learningmap-selected');\n }\n firstPlace = null;\n lastTarget = secondPlace;\n secondPlace = null;\n }\n } else {\n unselectAll();\n firstPlace = null;\n }\n }\n /**\n * Removes the classes 'learningmap-selected' and 'learningmap-selectet-activity-selector'\n * from all nodes\n */\n function unselectAll() {\n Array.from(document.getElementsByClassName('learningmap-selected')).forEach(function(e) {\n e.classList.remove('learningmap-selected');\n });\n Array.from(document.getElementsByClassName('learningmap-selected-activity-selector')).forEach(function(e) {\n e.classList.remove('learningmap-selected-activity-selector');\n });\n }\n\n /**\n * Adds a path between two places.\n * @param {number} fid id of the first place (meant to be the smaller one)\n * @param {number} sid id of the second place (meant to be the bigger one)\n */\n function addPath(fid, sid) {\n let pid = 'p' + fid + '_' + sid;\n if (document.getElementById(pid) === null) {\n let pathsgroup = document.getElementById('pathsGroup');\n if (!pathsgroup) {\n pathsgroup = document.getElementById('pathsGroup-' + placestore.getMapid());\n }\n let first = document.getElementById('p' + fid);\n let second = document.getElementById('p' + sid);\n if (pathsgroup && first && second) {\n pathsgroup.appendChild(\n path(\n first.cx.baseVal.value,\n first.cy.baseVal.value,\n second.cx.baseVal.value,\n second.cy.baseVal.value,\n 'learningmap-path',\n pid\n )\n );\n placestore.addPath(pid, 'p' + fid, 'p' + sid);\n }\n }\n }\n\n /**\n * Removes a place from the SVG and the placestore. This function also removes all\n * touching paths and entries in startingplaces / targetplaces linking to the removed\n * place.\n * @param {any} event event causing the remove order\n */\n function removePlace(event) {\n let place = document.getElementById(event.target.id);\n let parent = place.parentNode;\n removePathsTouchingPlace(event.target.id);\n placestore.removePlace(event.target.id);\n parent.removeChild(place);\n parent.parentNode.removeChild(parent);\n let textNode = document.getElementById('text' + event.target.id);\n if (textNode) {\n textNode.parentNode.removeChild(textNode);\n }\n updateCode();\n }\n\n /**\n * Removes all paths touching a certain place\n * @param {number} id id of the place\n */\n function removePathsTouchingPlace(id) {\n placestore.getTouchingPaths(id).forEach(\n function(e) {\n removePath(e.id);\n }\n );\n }\n\n /**\n * Removes a path from the SVG and from the placestore\n * @param {number} id id of the path\n */\n function removePath(id) {\n let path = document.getElementById(id);\n if (path !== null) {\n path.parentNode.removeChild(path);\n placestore.removePath(id);\n }\n }\n\n /**\n * Sets the background image of the SVG to the current image in filemanager.\n */\n function refreshBackgroundImage() {\n let previewimage = document.getElementsByClassName('realpreview');\n if (previewimage.length > 0) {\n let background = document.getElementById('learningmap-background-image');\n if (!background) {\n background = document.getElementById('learningmap-background-image-' + placestore.getMapid());\n }\n let backgroundurl = previewimage[0].getAttribute('src').split('?')[0];\n // If the uploaded file reuses the filename of a previously uploaded image, they differ\n // only in the oid. So one has to append the oid to the url.\n if (previewimage[0].getAttribute('src').split('?')[1].includes('&oid=')) {\n backgroundurl += '?oid=' + previewimage[0].getAttribute('src').split('&oid=')[1];\n }\n background.setAttribute('xlink:href', backgroundurl);\n }\n }\n\n /**\n * Adds an eventListener to the background image for watching file changes and updating\n * height and width of the image.\n */\n function registerBackgroundListener() {\n let background = document.getElementById('learningmap-background-image');\n if (!background) {\n background = document.getElementById('learningmap-background-image-' + placestore.getMapid());\n }\n if (background) {\n background.addEventListener('load', function() {\n background.removeAttribute('height');\n let height = parseInt(background.getBBox().height);\n let width = background.getBBox().width;\n placestore.setBackgroundDimensions(width, height);\n svgnode.setAttribute('viewBox', '0 0 ' + placestore.width + ' ' + placestore.height);\n background.setAttribute('width', width);\n background.setAttribute('height', height);\n updateCode();\n });\n }\n }\n\n /**\n * Updates CSS code inside the SVG (called, when one of the colors is changed).\n * Calls updateCode() when completed.\n */\n function updateCSS() {\n Templates.renderForPromise('mod_learningmap/cssskeleton', placestore.getPlacestore())\n .then(({html, js}) => {\n Templates.replaceNode('#learningmap-svgstyle', html, js);\n updateCode();\n return true;\n })\n .catch(ex => displayException(ex));\n }\n\n /**\n * Updates the activity selector to highlight the activities already used\n * and to show the alert for hidden activities.\n */\n function updateActivities() {\n let activities = placestore.getAllActivities();\n let options = Array.from(activitySelector.getElementsByTagName('option'));\n activityHiddenWarning.setAttribute('hidden', '');\n options.forEach(function(n) {\n if (activities.includes(n.value)) {\n n.classList.add('learningmap-used-activity');\n if (n.selected) {\n if (n.getAttribute('data-activity-hidden') == true) {\n activityHiddenWarning.removeAttribute('hidden');\n }\n }\n } else {\n n.classList.remove('learningmap-used-activity');\n }\n });\n }\n\n /**\n * Adds the event listener to the color chooser buttons.\n * @param {*} name name of the color\n * @param {*} secondValue name of a second placestore value that has to be changed along\n */\n function colorChooserLogic(name, secondValue = '') {\n let colorChooser = document.getElementById('learningmap-color-' + name);\n if (colorChooser) {\n colorChooser.addEventListener('change', function() {\n placestore.setColor(name, colorChooser.value);\n if (secondValue != '') {\n placestore.setColor(secondValue, colorChooser.value);\n }\n updateCSS();\n });\n colorChooser.value = placestore.getColor(name);\n }\n }\n\n /**\n * Adds the event listener to advanced settings menu items\n * @param {*} name Name of the item\n * @param {*} getCall Method of placestore to call to read value\n * @param {*} setCall Method of placestore to call to save value\n * @param {*} callback Additional callback after value is saved\n */\n async function advancedSettingsLogic(name, getCall, setCall, callback = null) {\n let settingItem = document.getElementById('learningmap-advanced-setting-' + name);\n if (settingItem) {\n // Settings that need a modal for editing (e.g. title and description) have a link to\n // open the modal, other settings have a checkbox.\n if (settingItem.nodeName == 'A') {\n const descriptionHandler = async() => {\n const values = getCall();\n try {\n const strings = await Str.get_strings([\n {key: 'titleanddescription', component: 'mod_learningmap'},\n {key: 'save', component: 'core'},\n ]);\n return saveCancel(\n strings[0],\n Templates.render('mod_learningmap/' + name + '-modal', values),\n strings[1],\n () => {\n let values = {};\n let formentries = document.querySelectorAll('.mod_learningmap_' + name + '_value');\n formentries.forEach((element) => {\n values[element.name] = element.value;\n });\n setCall(placestore, values);\n if (callback !== null) {\n callback();\n }\n updateCSS();\n }\n );\n } catch (ex) {\n displayException(ex);\n return null;\n }\n };\n settingItem.addEventListener('click', descriptionHandler);\n settingItem.addEventListener('keypress', (e) => {\n if (e.key === 'Enter') {\n descriptionHandler();\n }\n });\n // Boolean settings can be directly changed with a checkbox in the advanced settings menu.\n } else {\n settingItem.checked = getCall.call(placestore);\n settingItem.addEventListener('change', function() {\n setCall.call(placestore, settingItem.checked);\n if (callback !== null) {\n callback();\n }\n updateCSS();\n });\n }\n }\n }\n\n /**\n * Adds missing text nodes\n */\n function fixPlaceLabels() {\n let options = Array.from(activitySelector.getElementsByTagName('option'));\n let places = placestore.getPlaces();\n for (const place of places) {\n if (document.getElementById('text' + place.id) === null) {\n let content = '';\n for (const option of options) {\n if (option.value == place.linkedActivity) {\n content = option.textContent;\n break;\n }\n }\n let placeNode = document.getElementById(place.id);\n let textGroup = document.getElementById('textsGroup-' + placestore.getMapid());\n let textNode = text('text' + place.id, content, placeNode.cx.baseVal.value, placeNode.cy.baseVal.value);\n textGroup.appendChild(textNode);\n }\n }\n }\n\n /**\n * Hides the advanced settings menu.\n */\n function hideAdvancedSettings() {\n let advancedSettings = document.getElementById('learningmap-advanced-settings');\n advancedSettings.setAttribute('hidden', '');\n }\n\n /**\n * Returns the place that belongs to the given text id.\n * @param {*} textId\n * @returns {*} The place element\n */\n function findPlaceForText(textId) {\n let placename = textId.replace('text', '');\n return svgnode.getElementById(placename);\n }\n};\n"],"names":["targetPoints","pathTypes","async","offset","dragel","pathsToUpdateFirstPoint","pathsToUpdateSecondPoint","prefetchTemplates","selectedElement","firstPlace","secondPlace","lastTarget","elementForActivitySelector","touchstart","touchend","touchmove","mapdiv","document","getElementById","code","svgdoc","DOMParser","parseFromString","value","svgnode","querySelector","activitySetting","activitySelector","activityStarting","activityTarget","activityHiddenWarning","advancedSettingsIcon","treeView","setAttribute","iconView","setTimeout","dispatchEvent","Event","addEventListener","setActivityId","text","replaceChildren","createTextNode","textContent","title","classList","remove","add","updateActivities","updateCode","checked","addStartingPlace","removeStartingPlace","addTargetPlace","removeTargetPlace","placestoreInput","getElementsByName","loadJSON","advancedSettings","getAttribute","hideAdvancedSettings","removeAttribute","hideContextMenu","e","key","preventDefault","click","advancedSettingsClose","advancedSettingsLogic","placestore","getHidePaths","setHidePaths","getUseCheckmark","setUseCheckmark","getHover","setHover","getPulse","setPulse","getShowall","setShowall","getHideStroke","setHideStroke","getShowText","setShowText","options","Array","from","getElementsByTagName","places","getPlaces","place","id","content","option","linkedActivity","placeNode","textGroup","getMapid","textNode","cx","baseVal","cy","appendChild","getSliceMode","setSliceMode","getShowWayGone","setShowWayGone","desc","titlenode","descnode","description","values","createElementNS","titlecontent","desccontent","showContextMenu","unselectAll","target","touches","contains","activityId","getActivityId","scalingFactor","clientWidth","style","setProperty","clientHeight","display","isStartingPlace","isTargetPlace","colorChooserLogic","refreshBackgroundImage","background","height","parseInt","getBBox","width","setBackgroundDimensions","registerBackgroundListener","el","startDrag","drag","endDrag","evt","cancelable","nodeName","dblclickHandler","endTouch","getMousePosition","x","getAttributeNS","y","getPathsWithFid","getPathsWithSid","findPlaceForText","pathPoint","transformCoordinates","layerX","layerY","coord","dx","dy","setAttributeNS","updatePathDeclaration","forEach","path","pathNode","clickHandler","oldDefinition","targetX","targetY","targetP","parts","split","fromX","fromY","toX","toY","bezierX","bezierY","pathType","i","length","makeDraggable","updateCSS","backgroundfileNode","MutationObserver","observe","attributes","childList","subtree","clientX","clientY","CTM","getScreenCTM","a","f","d","innerHTML","JSON","stringify","getPlacestore","event","placesgroup","textgroup","placeId","getId","linkId","child","link","r","classes","circle","addPlace","parent","parentNode","getTouchingPaths","removePath","removePlace","removeChild","circleRadius","textcontent","fid","replace","sid","z","pid","pathsgroup","first","second","x1","y1","x2","y2","addPath","getElementsByClassName","previewimage","backgroundurl","includes","renderForPromise","then","_ref","html","js","replaceNode","catch","ex","activities","getAllActivities","n","selected","name","secondValue","colorChooser","setColor","getColor","getCall","setCall","callback","settingItem","descriptionHandler","strings","Str","get_strings","component","Templates","render","querySelectorAll","element","call","textId","placename"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/learningmap.js b/amd/src/learningmap.js index 59a73904..1e6957ae 100644 --- a/amd/src/learningmap.js +++ b/amd/src/learningmap.js @@ -17,7 +17,7 @@ * Main module for the learningmap editor * * @module mod_learningmap/learningmap - * @copyright 2025 ISB Bayern + * @copyright 2021-2026 ISB Bayern * @author Stefan Hanauska * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -25,6 +25,7 @@ import {exception as displayException, saveCancel} from 'core/notification'; import Templates from 'core/templates'; import placestore from 'mod_learningmap/placestore'; import * as Str from 'core/str'; +import {debounce} from 'core/utils'; const circleRadius = 10; @@ -108,13 +109,13 @@ export const init = async() => { if (activitySelector.value) { let text = document.getElementById('text' + elementForActivitySelector); if (text) { - text.replaceChildren(svgdoc.createCDATASection( + text.replaceChildren(svgdoc.createTextNode( activitySelector.querySelector('option[value="' + activitySelector.value + '"]').textContent )); } let title = document.getElementById('title' + elementForActivitySelector); if (title) { - title.replaceChildren(svgdoc.createCDATASection( + title.replaceChildren(svgdoc.createTextNode( activitySelector.querySelector('option[value="' + activitySelector.value + '"]').textContent )); } @@ -306,11 +307,11 @@ export const init = async() => { dragel = el; if (el) { el.addEventListener('mousedown', startDrag); - el.addEventListener('mousemove', drag); + el.addEventListener('mousemove', debounce(drag, 5)); el.addEventListener('mouseup', endDrag); el.addEventListener('mouseleave', endDrag); el.addEventListener('touchstart', startTouch); - el.addEventListener('touchmove', drag); + el.addEventListener('touchmove', debounce(drag, 5)); el.addEventListener('touchend', endTouch); el.addEventListener('touchleave', endTouch); el.addEventListener('touchcancel', endTouch); @@ -336,7 +337,7 @@ export const init = async() => { pathsToUpdateSecondPoint = placestore.getPathsWithSid(selectedElement.id); } else if (evt.target.nodeName == 'text') { selectedElement = evt.target; - let place = selectedElement.parentNode.querySelector('.learningmap-place'); + let place = findPlaceForText(selectedElement.id); offset = getMousePosition(evt); offset.x -= parseInt(selectedElement.getAttributeNS(null, "dx")) + place.cx.baseVal.value; offset.y -= parseInt(selectedElement.getAttributeNS(null, "dy")) + place.cy.baseVal.value; @@ -365,7 +366,7 @@ export const init = async() => { let cx = coord.x - offset.x; let cy = coord.y - offset.y; if (selectedElement.nodeName == 'text') { - let place = selectedElement.parentNode.querySelector('.learningmap-place'); + let place = findPlaceForText(selectedElement.id); // Calculate the delta from the current mouse position to the corresponding place. // coord: current mouse position // offset: delta from the mouse position to the coordinates of the text node @@ -641,7 +642,7 @@ export const init = async() => { // Default value for delta: Circle radius * 1.5 (as a padding) text.setAttribute('dx', circleRadius * 1.5); text.setAttribute('dy', circleRadius * 1.5); - let textcontent = svgdoc.createCDATASection(content); + let textcontent = svgdoc.createTextNode(content); text.replaceChildren(textcontent); return text; } @@ -689,10 +690,9 @@ export const init = async() => { * @param {*} child child item to set the link on * @param {*} id id of the link * @param {*} title title of the link - * @param {*} text text to describe the link * @returns {any} */ - function link(child, id, title = null, text = null) { + function link(child, id, title = null) { let link = document.createElementNS('http://www.w3.org/2000/svg', 'a'); link.setAttribute('id', id); link.setAttribute('xlink:href', ''); @@ -701,9 +701,6 @@ export const init = async() => { if (title !== null) { link.appendChild(title); } - if (text !== null) { - link.appendChild(text); - } return link; } @@ -718,7 +715,7 @@ export const init = async() => { titlenode = document.createElementNS('http://www.w3.org/2000/svg', 'title'); svgnode.appendChild(titlenode); } - let titlecontent = svgdoc.createCDATASection(values.title); + let titlecontent = svgdoc.createTextNode(values.title); titlenode.replaceChildren(titlecontent); titlenode.setAttribute('id', 'title-' + placestore.getMapid()); @@ -727,7 +724,7 @@ export const init = async() => { descnode = document.createElementNS('http://www.w3.org/2000/svg', 'desc'); svgnode.appendChild(descnode); } - let desccontent = svgdoc.createCDATASection(values.description); + let desccontent = svgdoc.createTextNode(values.description); descnode.replaceChildren(desccontent); descnode.setAttribute('id', 'desc-' + placestore.getMapid()); } @@ -760,6 +757,15 @@ export const init = async() => { */ function addPlace(event) { let placesgroup = document.getElementById('placesGroup'); + if (!placesgroup) { + placesgroup = document.getElementById('placesGroup-' + placestore.getMapid()); + } + let textgroup = document.getElementById('textsGroup-' + placestore.getMapid()); + if (!textgroup) { + textgroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + textgroup.setAttribute('id', 'textsGroup-' + placestore.getMapid()); + svgnode.appendChild(textgroup); + } let placeId = 'p' + placestore.getId(); let linkId = 'a' + placestore.getId(); var CTM = event.target.getScreenCTM(); @@ -772,10 +778,12 @@ export const init = async() => { link( circle(cx, cy, circleRadius, 'learningmap-place learningmap-draggable learningmap-emptyplace', placeId), linkId, - title('title' + placeId), - text('text' + placeId, '', cx, cy) + title('title' + placeId) ) ); + textgroup.appendChild( + text('text' + placeId, '', cx, cy) + ); placestore.addPlace(placeId, linkId); } @@ -840,6 +848,9 @@ export const init = async() => { let pid = 'p' + fid + '_' + sid; if (document.getElementById(pid) === null) { let pathsgroup = document.getElementById('pathsGroup'); + if (!pathsgroup) { + pathsgroup = document.getElementById('pathsGroup-' + placestore.getMapid()); + } let first = document.getElementById('p' + fid); let second = document.getElementById('p' + sid); if (pathsgroup && first && second) { @@ -871,7 +882,10 @@ export const init = async() => { placestore.removePlace(event.target.id); parent.removeChild(place); parent.parentNode.removeChild(parent); - + let textNode = document.getElementById('text' + event.target.id); + if (textNode) { + textNode.parentNode.removeChild(textNode); + } updateCode(); } @@ -906,6 +920,9 @@ export const init = async() => { let previewimage = document.getElementsByClassName('realpreview'); if (previewimage.length > 0) { let background = document.getElementById('learningmap-background-image'); + if (!background) { + background = document.getElementById('learningmap-background-image-' + placestore.getMapid()); + } let backgroundurl = previewimage[0].getAttribute('src').split('?')[0]; // If the uploaded file reuses the filename of a previously uploaded image, they differ // only in the oid. So one has to append the oid to the url. @@ -922,6 +939,9 @@ export const init = async() => { */ function registerBackgroundListener() { let background = document.getElementById('learningmap-background-image'); + if (!background) { + background = document.getElementById('learningmap-background-image-' + placestore.getMapid()); + } if (background) { background.addEventListener('load', function() { background.removeAttribute('height'); @@ -1069,8 +1089,9 @@ export const init = async() => { } } let placeNode = document.getElementById(place.id); + let textGroup = document.getElementById('textsGroup-' + placestore.getMapid()); let textNode = text('text' + place.id, content, placeNode.cx.baseVal.value, placeNode.cy.baseVal.value); - placeNode.parentNode.appendChild(textNode); + textGroup.appendChild(textNode); } } } @@ -1082,4 +1103,14 @@ export const init = async() => { let advancedSettings = document.getElementById('learningmap-advanced-settings'); advancedSettings.setAttribute('hidden', ''); } + + /** + * Returns the place that belongs to the given text id. + * @param {*} textId + * @returns {*} The place element + */ + function findPlaceForText(textId) { + let placename = textId.replace('text', ''); + return svgnode.getElementById(placename); + } }; diff --git a/backup/moodle2/restore_learningmap_activity_task.class.php b/backup/moodle2/restore_learningmap_activity_task.class.php index 2b8ee585..b6b91fb3 100644 --- a/backup/moodle2/restore_learningmap_activity_task.class.php +++ b/backup/moodle2/restore_learningmap_activity_task.class.php @@ -104,8 +104,8 @@ public function after_restore(): void { $newmapid = uniqid(); $placestore->mapid = $newmapid; - if (!isset($placestore->version) || $placestore->version < 2024072201) { - $placestore->version = 2024072201; + if (!isset($placestore->version) || $placestore->version < 2026022200) { + $placestore->version = 2026022200; // Needs 1 as default value (otherwise all place strokes would be hidden). if (!isset($placestore->strokeopacity)) { $placestore->strokeopacity = 1; @@ -121,9 +121,11 @@ public function after_restore(): void { $mapworker = new \mod_learningmap\mapworker($mapcode, (array)$placestore); $mapworker->replace_stylesheet(); $mapworker->replace_defs(); + $mapworker->fix_svg(); $item->svgcode = $mapworker->get_svgcode(); } - $item->svgcode = str_replace('learningmap-svgmap-' . $oldmapid, 'learningmap-svgmap-' . $newmapid, $item->svgcode); + // Map ids are used in the svg code as well, so we need to replace them there too. + $item->svgcode = str_replace('-' . $oldmapid, '-' . $newmapid, $item->svgcode); $item->placestore = json_encode($placestore); $item->course = $courseid; $DB->update_record('learningmap', $item); diff --git a/backup/moodle2/restore_learningmap_stepslib.php b/backup/moodle2/restore_learningmap_stepslib.php index 9de82ff4..a135f124 100644 --- a/backup/moodle2/restore_learningmap_stepslib.php +++ b/backup/moodle2/restore_learningmap_stepslib.php @@ -35,7 +35,8 @@ protected function define_structure(): array { } /** - * Restore a learningmap record. + * Restore a learningmap record. This will not change placestore and + * svgcode as they will be processed in the after_restore method. * @param array|object $data * @throws base_step_exception * @throws dml_exception diff --git a/classes/mapworker.php b/classes/mapworker.php index dda46c8d..b91658a7 100644 --- a/classes/mapworker.php +++ b/classes/mapworker.php @@ -78,22 +78,6 @@ public function __construct( int $group = 0 ) { global $USER; - $svgcode = preg_replace('/(<\!--\[CDATA\[)(.*?)(\]\]-->)/', '', $svgcode); - $svgcode = preg_replace( - '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/text>/', - '', - $svgcode - ); - $svgcode = preg_replace( - '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/title>/', - '', - $svgcode - ); - $svgcode = preg_replace( - '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/desc>/', - '', - $svgcode - ); $this->edit = $edit; $placestore['editmode'] = $this->edit; $this->placestore = $placestore; @@ -122,7 +106,7 @@ public function replace_stylesheet(array $placestoreoverride = []): void { * @return void */ public function replace_defs(): void { - $this->svgmap->replace_defs(); + $this->svgmap->replace_defs(['mapid' => $this->placestore['mapid']]); } /** @@ -265,6 +249,7 @@ public function process_map_objects(): void { } else { $this->svgmap->set_hidden($links[$place]); $this->svgmap->remove_link($links[$place]); + $this->svgmap->remove_attribute($links[$place], 'data-cmid'); } } // Remove all places that are impossible to reach. @@ -324,7 +309,6 @@ public function is_path_between(string $place1, string $place2): ?string { * @return string */ public function get_svgcode(): string { - $this->svgmap->replace_cdata(); return $this->svgmap->get_svgcode(); } @@ -346,4 +330,14 @@ public function get_attribute(string $id, string $attribute): ?string { public function get_active(): array { return $this->active; } + + /** + * Helper function for upgrade. Fixes the SVG code of existing maps to ensure that the groups for paths, + * places and background are present and have the correct attributes. + * + * @return void + */ + public function fix_svg(): void { + $this->svgmap->fix_svg(); + } } diff --git a/classes/svgmap.php b/classes/svgmap.php index 6f203b8b..cc7e3c5d 100644 --- a/classes/svgmap.php +++ b/classes/svgmap.php @@ -63,14 +63,47 @@ class svgmap { */ public function __construct(string $svgcode, array $placestore) { $this->svgcode = $svgcode; + // Fix some common problems with SVG code that can cause issues with DOMDocument. + $this->svgcode = preg_replace_callback('/(<\!--\[CDATA\[)(.*?)(\]\]-->)/s', function ($matches) { + return self::escape_content($matches[2]); + }, $this->svgcode); + $this->svgcode = preg_replace_callback( + '//s', + function ($matches) { + return self::escape_content($matches[1]); + }, + $this->svgcode + ); + $this->svgcode = preg_replace_callback( + '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/text>/s', + function ($matches) { + return '' . self::escape_content($matches[3]) . ''; + }, + $this->svgcode + ); + $this->svgcode = preg_replace_callback( + '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/title>/s', + function ($matches) { + return '' . self::escape_content($matches[3]) . ''; + }, + $this->svgcode + ); + $this->svgcode = preg_replace_callback( + '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/desc>/s', + function ($matches) { + return '' . self::escape_content($matches[3]) . ''; + }, + $this->svgcode + ); + $this->placestore = $placestore; $this->dom = new \DOMDocument('1.0', 'UTF-8'); $this->dom->preserveWhiteSpace = false; $this->dom->formatOutput = true; + $this->dom->recover = true; $this->load_dom(); - $this->xpath = new \DOMXPath($this->dom); } /** @@ -81,6 +114,7 @@ public function __construct(string $svgcode, array $placestore) { public function load_dom(): void { $this->remove_tags_before_svg(); $this->dom->loadXML($this->svgcode); + $this->xpath = new \DOMXPath($this->dom); } /** @@ -102,14 +136,14 @@ public function replace_stylesheet(array $placestoreoverride = []): void { /** * Replaces the svg defs (e.g.) filters or patterns that are defined for use in the document without being directly visible. - * + * @param array $context Context data for the template * @return void */ - public function replace_defs(): void { + public function replace_defs(array $context = []): void { global $OUTPUT; $this->svgcode = preg_replace( '//i', - $OUTPUT->render_from_template('mod_learningmap/svgdefs', []), + $OUTPUT->render_from_template('mod_learningmap/svgdefs', $context), $this->svgcode ); $this->load_dom(); @@ -173,6 +207,11 @@ public function remove_place_or_path(string $id): void { } // Make sure that also the link node is removed. $placeorpath = $placeorpath->parentNode; + // Remove text. + $text = $this->get_element_by_id('text' . $id); + if ($text) { + $text->parentNode->removeChild($text); + } } $placeorpath->parentNode->removeChild($placeorpath); } @@ -333,7 +372,13 @@ public function get_coordinates(): array { global $CFG; $coordinates = []; $pathsgroup = $this->get_element_by_id('pathsGroup'); + if (!$pathsgroup) { + $pathsgroup = $this->get_element_by_id('pathsGroup-' . $this->placestore['mapid']); + } $placesgroup = $this->get_element_by_id('placesGroup'); + if (!$placesgroup) { + $placesgroup = $this->get_element_by_id('placesGroup-' . $this->placestore['mapid']); + } if (empty($this->placestore['hidepaths'])) { // Only processing quadratic bezier curves here as other paths are already handled // via the coordinates of the corresponding places. @@ -360,7 +405,7 @@ public function get_coordinates(): array { $cx = intval($placenode->getAttribute('cx')); $cy = intval($placenode->getAttribute('cy')); $coordinates[] = ['x' => $cx, 'y' => $cy]; - if ($this->placestore['showtext']) { + if (!empty($this->placestore['showtext'])) { $text = $this->get_element_by_id('text' . $placenode->getAttribute('id')); if ($text) { // Delta of the text in relation to the places center coordinates. @@ -388,6 +433,9 @@ public function add_overlay(): void { $coordinates = $this->get_coordinates(); if (count($coordinates) > 0) { $backgroundnode = $this->get_element_by_id('learningmap-background-image'); + if (!$backgroundnode) { + $backgroundnode = $this->get_element_by_id('learningmap-background-image-' . $this->placestore['mapid']); + } $height = $backgroundnode->getAttribute('height'); $c = array_pop($coordinates); $minx = $c['x']; @@ -416,7 +464,9 @@ public function add_overlay(): void { $maxy = min($height, $maxy + $padding); $placesgroup = $this->get_element_by_id('placesGroup'); - + if (!$placesgroup) { + $placesgroup = $this->get_element_by_id('placesGroup-' . $this->placestore['mapid']); + } // Create the overlay for slicemode. $overlay = $this->dom->createElement('path'); $overlaydescription = "M 0 0 L 0 $height L 800 $height L 800 0 Z "; @@ -535,6 +585,20 @@ public function set_attribute(string $id, string $attribute, string $value): voi } } + /** + * Removes an attribute of an element. + * + * @param string $id The id of the DOM element + * @param string $attribute The name of the attribute + * @return void + */ + public function remove_attribute(string $id, string $attribute): void { + $element = $this->get_element_by_id($id); + if ($element) { + $element->removeAttribute($attribute); + } + } + /** * Replaces all CDATA sections with properly escaped content * @@ -560,4 +624,47 @@ function ($matches) { public static function escape_content(string $content): string { return htmlspecialchars($content, ENT_QUOTES | ENT_XML1, 'UTF-8', false); } + + /** + * Helper function for upgrade. Renames groups to have individual ids and moves text elements to a separate group. + * + * @return void + */ + public function fix_svg(): void { + $svgmap = $this->get_element_by_id('learningmap-svgmap-' . $this->placestore['mapid']); + if (!$svgmap) { + $svgmap = $this->dom->getElementsByTagName('svg')->item(0); + $svgmap->setAttribute('id', 'learningmap-svgmap-' . $this->placestore['mapid']); + } + $placesgroup = $this->get_element_by_id('placesGroup'); + if ($placesgroup) { + $placesgroup->setAttribute('id', 'placesGroup-' . $this->placestore['mapid']); + $placesgroup->setAttribute('class', 'learningmap-places-group'); + } + $pathsgroup = $this->get_element_by_id('pathsGroup'); + if ($pathsgroup) { + $pathsgroup->setAttribute('mask', 'url(#placemask-' . $this->placestore['mapid'] . ')'); + $pathsgroup->setAttribute('id', 'pathsGroup-' . $this->placestore['mapid']); + $pathsgroup->setAttribute('class', 'learningmap-paths-group'); + } + $backgroundgroup = $this->get_element_by_id('backgroundGroup'); + if ($backgroundgroup) { + $backgroundgroup->setAttribute('id', 'backgroundGroup-' . $this->placestore['mapid']); + $backgroundgroup->setAttribute('class', 'learningmap-background-group'); + } + $textgroup = $this->get_element_by_id('textGroup-' . $this->placestore['mapid']); + if (!$textgroup) { + $textgroup = $this->dom->createElement('g'); + $textgroup->setAttribute('id', 'textGroup-' . $this->placestore['mapid']); + $textgroup->setAttribute('class', 'learningmap-text-group'); + $svgmap->appendChild($textgroup); + } + $textelements = $this->dom->getElementsByTagName('text'); + foreach ($textelements as $textelement) { + if (str_starts_with($textelement->getAttribute('id'), 'textp')) { + $textgroup->appendChild($textelement); + } + } + $this->save_svg_data(); + } } diff --git a/db/upgrade.php b/db/upgrade.php index 2c05b64f..8e60f6be 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -111,5 +111,25 @@ function xmldb_learningmap_upgrade($oldversion) { upgrade_mod_savepoint(true, 2025092200, 'learningmap'); } + if ($oldversion < 2026022200) { + $entries = $DB->get_records('learningmap', []); + if ($entries) { + foreach ($entries as $entry) { + $placestore = json_decode($entry->placestore, true); + $placestore['version'] = 2026022200; + $mapworker = new \mod_learningmap\mapworker($entry->svgcode, $placestore); + $mapworker->replace_stylesheet(); + $mapworker->replace_defs(); + $mapworker->fix_svg(); + $entry->svgcode = $mapworker->get_svgcode(); + $entry->placestore = json_encode($placestore); + $DB->update_record('learningmap', $entry); + } + } + + // Learningmap savepoint reached. + upgrade_mod_savepoint(true, 2026022200, 'learningmap'); + } + return true; } diff --git a/mod_form.php b/mod_form.php index ad9c7892..48c21aeb 100644 --- a/mod_form.php +++ b/mod_form.php @@ -45,28 +45,23 @@ public function definition(): void { $s = []; $activitysel = []; - // Gets only sections with content. - foreach ($cm->get_sections() as $sectionnum => $section) { - $sectioninfo = $cm->get_section_info($sectionnum); - $s['name'] = $sectioninfo->name; - if (empty($s['name'])) { - $s['name'] = get_string('section') . ' ' . $sectionnum; - } + $format = course_get_format($this->_course); + foreach ($cm->get_section_info_all() as $sectionnum => $sectioninfo) { + $s['name'] = $format->get_section_name($sectionnum); $s['coursemodules'] = []; - foreach ($section as $cmid) { - $module = $cm->get_cm($cmid); + foreach ($sectioninfo->get_sequence_cm_infos() as $module) { if ($CFG->branch >= 500 && !plugin_supports('mod', $module->modname, FEATURE_CAN_DISPLAY, true)) { continue; } - if (isset($this->_cm->id) && $this->_cm->id == $cmid) { + if (isset($this->_cm->id) && $this->_cm->id == $module->id) { // Do not include the learningmap itself. continue; } // Get only course modules which are not deleted. if ($module->deletioninprogress == 0) { $s['coursemodules'][] = [ - 'id' => $cmid, - 'name' => s($module->name), + 'id' => $module->id, + 'name' => $module->get_formatted_name(), 'completionenabled' => $module->completion > 0, 'hidden' => $module->visible == 0, ]; @@ -291,6 +286,8 @@ public function data_preprocessing(&$defaultvalues): void { ); $mapworker->process_map_objects(); $mapworker->replace_stylesheet(); + $mapworker->replace_defs(); + $mapworker->fix_svg(); $defaultvalues['svgcode'] = $mapworker->get_svgcode(); $draftitemid = file_get_submitted_draft_itemid('backgroundfile'); diff --git a/templates/cssskeleton.mustache b/templates/cssskeleton.mustache index cfbcb122..a962bea6 100644 --- a/templates/cssskeleton.mustache +++ b/templates/cssskeleton.mustache @@ -50,7 +50,12 @@ "slicemode": false } }} -