diff --git a/amd/build/learningmap.min.js b/amd/build/learningmap.min.js index 175008d..ff0b227 100644 --- a/amd/build/learningmap.min.js +++ b/amd/build/learningmap.min.js @@ -1,3 +1,11 @@ -define("mod_learningmap/learningmap",["exports","core/notification","core/templates","mod_learningmap/placestore"],(function(_exports,_notification,_templates,_placestore){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_placestore=_interopRequireDefault(_placestore);const targetPoints_firstPoint=1,targetPoints_secondPoint=2,targetPoints_bezierPoint=3,pathTypes_line=1,pathTypes_quadraticbezier=2;_exports.init=()=>{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())}));let advancedSettingsClose=document.getElementById("learningmap-advanced-settings-close");advancedSettingsClose&&advancedSettingsClose.addEventListener("click",(function(){advancedSettings.setAttribute("hidden","")})),advancedSettingsLogic("hidepaths",_placestore.default.getHidePaths,_placestore.default.setHidePaths),advancedSettingsLogic("usecheckmark",_placestore.default.getUseCheckmark,_placestore.default.setUseCheckmark),advancedSettingsLogic("hover",_placestore.default.getHover,_placestore.default.setHover),advancedSettingsLogic("pulse",_placestore.default.getPulse,_placestore.default.setPulse),advancedSettingsLogic("showall",_placestore.default.getShowall,_placestore.default.setShowall),advancedSettingsLogic("hidestroke",_placestore.default.getHideStroke,_placestore.default.setHideStroke),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)}})),advancedSettingsLogic("slicemode",_placestore.default.getSliceMode,_placestore.default.setSliceMode),advancedSettingsLogic("showwaygone",_placestore.default.getShowWayGone,_placestore.default.setShowWayGone)}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.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))}function advancedSettingsLogic(name,getCall,setCall){let callback=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,settingItem=document.getElementById("learningmap-advanced-setting-"+name);settingItem&&(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","")}}})); +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}} +/** + * Main module for the learningmap editor + * + * @module mod_learningmap/learningmap + * @copyright 2025 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","")}}})); //# 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 feb8f03..02a49db 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":["import {exception as displayException} from 'core/notification';\nimport Templates from 'core/templates';\nimport placestore from 'mod_learningmap/placestore';\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 = () => {\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 let advancedSettingsClose = document.getElementById('learningmap-advanced-settings-close');\n if (advancedSettingsClose) {\n advancedSettingsClose.addEventListener('click', function() {\n advancedSettings.setAttribute('hidden', '');\n });\n }\n\n advancedSettingsLogic('hidepaths', placestore.getHidePaths, placestore.setHidePaths);\n advancedSettingsLogic('usecheckmark', placestore.getUseCheckmark, placestore.setUseCheckmark);\n advancedSettingsLogic('hover', placestore.getHover, placestore.setHover);\n advancedSettingsLogic('pulse', placestore.getPulse, placestore.setPulse);\n advancedSettingsLogic('showall', placestore.getShowall, placestore.setShowall);\n advancedSettingsLogic('hidestroke', placestore.getHideStroke, placestore.setHideStroke);\n advancedSettingsLogic('showtext', placestore.getShowText, placestore.setShowText, fixPlaceLabels);\n advancedSettingsLogic('slicemode', placestore.getSliceMode, placestore.setSliceMode);\n advancedSettingsLogic('showwaygone', placestore.getShowWayGone, placestore.setShowWayGone);\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.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 * 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 statringplaces / 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 function advancedSettingsLogic(name, getCall, setCall, callback = null) {\n let settingItem = document.getElementById('learningmap-advanced-setting-' + name);\n if (settingItem) {\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 * 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","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","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","showContextMenu","e","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","preventDefault","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","createElementNS","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","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 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 diff --git a/amd/build/linkmodal.min.js b/amd/build/linkmodal.min.js index aca1038..b6beaf1 100644 --- a/amd/build/linkmodal.min.js +++ b/amd/build/linkmodal.min.js @@ -1,10 +1,10 @@ -define("mod_learningmap/linkmodal",["exports","core/modal","core/ajax","core_course/manual_completion_toggle","mod_learningmap/renderer","core_course/events","jquery"],(function(_exports,_modal,_ajax,manualcompletion,_renderer,_events,_jquery){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/linkmodal",["exports","core/modal","core/ajax","core_course/manual_completion_toggle","mod_learningmap/renderer","core_course/events"],(function(_exports,_modal,_ajax,manualcompletion,_renderer,_events){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}} /** * Helper for opening course modules in a modal that do not have a view page. * * @module mod_learningmap/linkmodal * @copyright 2025 ISB Bayern * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=_interopRequireDefault(_modal),_ajax=_interopRequireDefault(_ajax),manualcompletion=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}(manualcompletion),_events=_interopRequireDefault(_events),_jquery=_interopRequireDefault(_jquery);_exports.init=async function(learningmapcmid){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const container=document.getElementById("learningmap-render-container-"+learningmapcmid+(inmodal?"-modal":""));container&&container.addEventListener("click",(async event=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();const cmid=target.getAttribute("data-cmid");if(cmid){const data=await _ajax.default.call([{methodname:"mod_learningmap_get_cm",args:{cmid:cmid}}])[0];let js=_jquery.default.parseHTML(data.js,null,!0).map((node=>node.innerHTML)).join("\n");const modal=await _modal.default.create({title:data.name,body:data.completion+data.html,show:!1,removeOnClose:!0,large:!0});modal.bodyJS=js,modal.show(),manualcompletion.init(),document.addEventListener(_events.default.manualCompletionToggled,(()=>{(0,_renderer.renderLearningmap)(learningmapcmid)}))}}}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.openModal=_exports.init=void 0,_modal=_interopRequireDefault(_modal),_ajax=_interopRequireDefault(_ajax),manualcompletion=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}(manualcompletion),_events=_interopRequireDefault(_events);_exports.init=async function(learningmapcmid){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const container=document.getElementById("learningmap-render-container-"+learningmapcmid+(inmodal?"-modal":""));container&&(container.addEventListener("click",(async event=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();target.getAttribute("data-cmid")&&await openModal(event,learningmapcmid,inmodal)}})),container.addEventListener("keydown",(async event=>{if("Enter"===event.key){const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();target.getAttribute("data-cmid")&&await openModal(event,learningmapcmid,inmodal)}}})))};const openModal=async(event,learningmapcmid,inmodal)=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();const cmid=target.getAttribute("data-cmid");if(cmid){const data=await _ajax.default.call([{methodname:"mod_learningmap_get_cm",args:{cmid:cmid}}])[0],container=document.createElement("div");container.innerHTML=data.js;let js=Array.from(container.querySelectorAll("script")).map((s=>s.textContent||"")).join("\n");const modal=await _modal.default.create({title:data.name,body:data.completion+data.html,show:!1,removeOnClose:!0,large:!0});modal.bodyJS=js,modal.show(),manualcompletion.init(),document.addEventListener(_events.default.manualCompletionToggled,(()=>{(0,_renderer.renderLearningmap)(learningmapcmid,inmodal)}))}}};_exports.openModal=openModal})); //# sourceMappingURL=linkmodal.min.js.map \ No newline at end of file diff --git a/amd/build/linkmodal.min.js.map b/amd/build/linkmodal.min.js.map index b54758a..87d14af 100644 --- a/amd/build/linkmodal.min.js.map +++ b/amd/build/linkmodal.min.js.map @@ -1 +1 @@ -{"version":3,"file":"linkmodal.min.js","sources":["../src/linkmodal.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\nimport Modal from 'core/modal';\nimport Ajax from 'core/ajax';\nimport * as manualcompletion from 'core_course/manual_completion_toggle';\nimport {renderLearningmap} from 'mod_learningmap/renderer';\nimport CourseEvents from 'core_course/events';\nimport $ from 'jquery';\n\n/**\n * Helper for opening course modules in a modal that do not have a view page.\n *\n * @module mod_learningmap/linkmodal\n * @copyright 2025 ISB Bayern\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Initialize the link modal listener for a learning map.\n *\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const init = async(learningmapcmid, inmodal = false) => {\n const container = document.getElementById('learningmap-render-container-' + learningmapcmid + (inmodal ? '-modal' : ''));\n if (container) {\n container.addEventListener('click', async(event) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n const data = await Ajax.call([{\n methodname: 'mod_learningmap_get_cm',\n args: {\n cmid: cmid\n },\n }])[0];\n let js = $.parseHTML(data.js, null, true).map(node => node.innerHTML).join(\"\\n\");\n const modal = await Modal.create({\n title: data.name,\n body: data.completion + data.html,\n show: false,\n removeOnClose: true,\n large: true,\n });\n modal.bodyJS = js;\n modal.show();\n manualcompletion.init();\n document.addEventListener(CourseEvents.manualCompletionToggled, () => {\n renderLearningmap(learningmapcmid);\n });\n }\n }\n });\n }\n};\n"],"names":["async","learningmapcmid","inmodal","container","document","getElementById","addEventListener","target","event","closest","hasAttribute","preventDefault","cmid","getAttribute","data","Ajax","call","methodname","args","js","$","parseHTML","map","node","innerHTML","join","modal","Modal","create","title","name","body","completion","html","show","removeOnClose","large","bodyJS","manualcompletion","init","CourseEvents","manualCompletionToggled"],"mappings":";;;;;;;06BAoCoBA,eAAMC,qBAAiBC,sEACjCC,UAAYC,SAASC,eAAe,gCAAkCJ,iBAAmBC,QAAU,SAAW,KAChHC,WACAA,UAAUG,iBAAiB,SAASN,MAAAA,cAC1BO,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,uBACAC,KAAOL,OAAOM,aAAa,gBAC7BD,KAAM,OACAE,WAAaC,cAAKC,KAAK,CAAC,CAC1BC,WAAY,yBACZC,KAAM,CACFN,KAAMA,SAEV,OACAO,GAAKC,gBAAEC,UAAUP,KAAKK,GAAI,MAAM,GAAMG,KAAIC,MAAQA,KAAKC,YAAWC,KAAK,YACrEC,YAAcC,eAAMC,OAAO,CAC7BC,MAAOf,KAAKgB,KACZC,KAAMjB,KAAKkB,WAAalB,KAAKmB,KAC7BC,MAAM,EACNC,eAAe,EACfC,OAAO,IAEXV,MAAMW,OAASlB,GACfO,MAAMQ,OACNI,iBAAiBC,OACjBnC,SAASE,iBAAiBkC,gBAAaC,yBAAyB,qCAC1CxC"} \ No newline at end of file +{"version":3,"file":"linkmodal.min.js","sources":["../src/linkmodal.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\nimport Modal from 'core/modal';\nimport Ajax from 'core/ajax';\nimport * as manualcompletion from 'core_course/manual_completion_toggle';\nimport {renderLearningmap} from 'mod_learningmap/renderer';\nimport CourseEvents from 'core_course/events';\n\n/**\n * Helper for opening course modules in a modal that do not have a view page.\n *\n * @module mod_learningmap/linkmodal\n * @copyright 2025 ISB Bayern\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Initialize the link modal listener for a learning map.\n *\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const init = async(learningmapcmid, inmodal = false) => {\n const container = document.getElementById('learningmap-render-container-' + learningmapcmid + (inmodal ? '-modal' : ''));\n if (container) {\n container.addEventListener('click', async(event) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n await openModal(event, learningmapcmid, inmodal);\n }\n }\n });\n container.addEventListener('keydown', async(event) => {\n if (event.key === 'Enter') {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n await openModal(event, learningmapcmid, inmodal);\n }\n }\n }\n });\n }\n};\n\n/**\n * Opens a modal for a course module link that does not have a view page.\n * @param {Event} event - The click or keydown event.\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const openModal = async(event, learningmapcmid, inmodal) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n const data = await Ajax.call([{\n methodname: 'mod_learningmap_get_cm',\n args: {\n cmid: cmid\n },\n }])[0];\n const container = document.createElement('div');\n container.innerHTML = data.js;\n let js = Array.from(container.querySelectorAll('script'))\n .map(s => s.textContent || '')\n .join('\\n');\n const modal = await Modal.create({\n title: data.name,\n body: data.completion + data.html,\n show: false,\n removeOnClose: true,\n large: true,\n });\n modal.bodyJS = js;\n modal.show();\n manualcompletion.init();\n document.addEventListener(CourseEvents.manualCompletionToggled, () => {\n renderLearningmap(learningmapcmid, inmodal);\n });\n }\n }\n};\n"],"names":["async","learningmapcmid","inmodal","container","document","getElementById","addEventListener","target","event","closest","hasAttribute","preventDefault","getAttribute","openModal","key","cmid","data","Ajax","call","methodname","args","createElement","innerHTML","js","Array","from","querySelectorAll","map","s","textContent","join","modal","Modal","create","title","name","body","completion","html","show","removeOnClose","large","bodyJS","manualcompletion","init","CourseEvents","manualCompletionToggled"],"mappings":";;;;;;;q5BAmCoBA,eAAMC,qBAAiBC,sEACjCC,UAAYC,SAASC,eAAe,gCAAkCJ,iBAAmBC,QAAU,SAAW,KAChHC,YACAA,UAAUG,iBAAiB,SAASN,MAAAA,cAC1BO,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,iBACOJ,OAAOK,aAAa,oBAEvBC,UAAUL,MAAOP,gBAAiBC,aAIpDC,UAAUG,iBAAiB,WAAWN,MAAAA,WAChB,UAAdQ,MAAMM,IAAiB,OACjBP,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,iBACOJ,OAAOK,aAAa,oBAEvBC,UAAUL,MAAOP,gBAAiBC,sBAcnDW,UAAYb,MAAMQ,MAAOP,gBAAiBC,iBAC7CK,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,uBACAI,KAAOR,OAAOK,aAAa,gBAC7BG,KAAM,OACAC,WAAaC,cAAKC,KAAK,CAAC,CAC1BC,WAAY,yBACZC,KAAM,CACFL,KAAMA,SAEV,GACEZ,UAAYC,SAASiB,cAAc,OACzClB,UAAUmB,UAAYN,KAAKO,OACvBA,GAAKC,MAAMC,KAAKtB,UAAUuB,iBAAiB,WAC1CC,KAAIC,GAAKA,EAAEC,aAAe,KAC1BC,KAAK,YACJC,YAAcC,eAAMC,OAAO,CAC7BC,MAAOlB,KAAKmB,KACZC,KAAMpB,KAAKqB,WAAarB,KAAKsB,KAC7BC,MAAM,EACNC,eAAe,EACfC,OAAO,IAEXV,MAAMW,OAASnB,GACfQ,MAAMQ,OACNI,iBAAiBC,OACjBxC,SAASE,iBAAiBuC,gBAAaC,yBAAyB,qCAC1C7C,gBAAiBC"} \ No newline at end of file diff --git a/amd/build/renderer.min.js b/amd/build/renderer.min.js index cec6760..2fc3ed3 100644 --- a/amd/build/renderer.min.js +++ b/amd/build/renderer.min.js @@ -6,6 +6,6 @@ define("mod_learningmap/renderer",["exports","core/ajax","core/log","core/pendin * @copyright 2021-2024, ISB Bayern * @author Philipp Memmel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.selectors=_exports.renderLearningmap=_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_log=_interopRequireDefault(_log),_pending=_interopRequireDefault(_pending);const selectors={LEARNINGMAP_RENDER_CONTAINER_PREFIX:"learningmap-render-container-"};_exports.selectors=selectors;_exports.init=function(cmId){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const rendererPendingPromise=new _pending.default("mod_learningmap/renderer-"+cmId);renderLearningmap(cmId,inmodal),rendererPendingPromise.resolve()};const renderLearningmap=cmId=>{_ajax.default.call([{methodname:"mod_learningmap_get_learningmap",args:{cmId:cmId}}])[0].then((data=>{let targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId);return targetDiv&&(targetDiv.innerHTML=data.content),targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId+"-modal"),targetDiv&&(targetDiv.innerHTML=data.content,targetDiv.parentElement.previousElementSibling.outerHTML=data.completion),!0})).catch((error=>(_log.default.error(error),!1)))};_exports.renderLearningmap=renderLearningmap})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.selectors=_exports.renderLearningmap=_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_log=_interopRequireDefault(_log),_pending=_interopRequireDefault(_pending);const selectors={LEARNINGMAP_RENDER_CONTAINER_PREFIX:"learningmap-render-container-"};_exports.selectors=selectors;_exports.init=function(cmId){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const rendererPendingPromise=new _pending.default("mod_learningmap/renderer-"+cmId);renderLearningmap(cmId,inmodal),rendererPendingPromise.resolve()};const renderLearningmap=cmId=>{_ajax.default.call([{methodname:"mod_learningmap_get_learningmap",args:{cmId:cmId}}])[0].then((data=>{let targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId),svgnode=(new DOMParser).parseFromString(data.content,"image/svg+xml").querySelector("svg");return targetDiv&&targetDiv.replaceChildren(svgnode),targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId+"-modal"),targetDiv&&(targetDiv.replaceChildren(svgnode),targetDiv.parentElement.previousElementSibling.outerHTML=data.completion),!0})).catch((error=>(_log.default.error(error),!1)))};_exports.renderLearningmap=renderLearningmap})); //# sourceMappingURL=renderer.min.js.map \ No newline at end of file diff --git a/amd/build/renderer.min.js.map b/amd/build/renderer.min.js.map index c05b43a..adb2c52 100644 --- a/amd/build/renderer.min.js.map +++ b/amd/build/renderer.min.js.map @@ -1 +1 @@ -{"version":3,"file":"renderer.min.js","sources":["../src/renderer.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 * Renderer module for the learningmap.\n *\n * @module mod_learningmap/renderer\n * @copyright 2021-2024, ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Log from 'core/log';\nimport Pending from 'core/pending';\n\nexport const selectors = {\n LEARNINGMAP_RENDER_CONTAINER_PREFIX: 'learningmap-render-container-'\n};\n\n/**\n * Renders the learningmap into the correct div.\n *\n * @param {number} cmId the course module id of the learningmap\n * @param {boolean} inmodal whether the learningmap is being rendered in a modal\n */\nexport const init = (cmId, inmodal = false) => {\n const rendererPendingPromise = new Pending('mod_learningmap/renderer-' + cmId);\n renderLearningmap(cmId, inmodal);\n rendererPendingPromise.resolve();\n};\n\n/**\n * Render the learningmap with the given cmId into the corresponding div in the DOM.\n *\n * @param {number} cmId the course module id of the learningmap\n */\nexport const renderLearningmap = (cmId) => {\n const promises = Ajax.call(\n [\n {\n methodname: 'mod_learningmap_get_learningmap',\n args: {\n 'cmId': cmId\n }\n }\n ]);\n\n promises[0].then(data => {\n let targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId);\n if (targetDiv) {\n targetDiv.innerHTML = data.content;\n }\n targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId + '-modal');\n if (targetDiv) {\n targetDiv.innerHTML = data.content;\n targetDiv.parentElement.previousElementSibling.outerHTML = data.completion;\n }\n return true;\n }).catch((error) => {\n Log.error(error);\n return false;\n });\n};\n"],"names":["selectors","LEARNINGMAP_RENDER_CONTAINER_PREFIX","cmId","inmodal","rendererPendingPromise","Pending","renderLearningmap","resolve","Ajax","call","methodname","args","then","data","targetDiv","document","getElementById","innerHTML","content","parentElement","previousElementSibling","outerHTML","completion","catch","error"],"mappings":";;;;;;;;sPA2BaA,UAAY,CACrBC,oCAAqC,4EASrB,SAACC,UAAMC,sEACjBC,uBAAyB,IAAIC,iBAAQ,4BAA8BH,MACzEI,kBAAkBJ,KAAMC,SACxBC,uBAAuBG,iBAQdD,kBAAqBJ,OACbM,cAAKC,KAClB,CACI,CACIC,WAAY,kCACZC,KAAM,MACMT,SAKf,GAAGU,MAAKC,WACTC,UAAYC,SAASC,eAAehB,UAAUC,oCAAsCC,aACpFY,YACAA,UAAUG,UAAYJ,KAAKK,SAE/BJ,UAAYC,SAASC,eAAehB,UAAUC,oCAAsCC,KAAO,UACvFY,YACAA,UAAUG,UAAYJ,KAAKK,QAC3BJ,UAAUK,cAAcC,uBAAuBC,UAAYR,KAAKS,aAE7D,KACRC,OAAOC,qBACFA,MAAMA,QACH"} \ No newline at end of file +{"version":3,"file":"renderer.min.js","sources":["../src/renderer.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 * Renderer module for the learningmap.\n *\n * @module mod_learningmap/renderer\n * @copyright 2021-2024, ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Log from 'core/log';\nimport Pending from 'core/pending';\n\nexport const selectors = {\n LEARNINGMAP_RENDER_CONTAINER_PREFIX: 'learningmap-render-container-'\n};\n\n/**\n * Renders the learningmap into the correct div.\n *\n * @param {number} cmId the course module id of the learningmap\n * @param {boolean} inmodal whether the learningmap is being rendered in a modal\n */\nexport const init = (cmId, inmodal = false) => {\n const rendererPendingPromise = new Pending('mod_learningmap/renderer-' + cmId);\n renderLearningmap(cmId, inmodal);\n rendererPendingPromise.resolve();\n};\n\n/**\n * Render the learningmap with the given cmId into the corresponding div in the DOM.\n *\n * @param {number} cmId the course module id of the learningmap\n */\nexport const renderLearningmap = (cmId) => {\n const promises = Ajax.call(\n [\n {\n methodname: 'mod_learningmap_get_learningmap',\n args: {\n 'cmId': cmId\n }\n }\n ]);\n\n promises[0].then(data => {\n let targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId);\n let svgdoc = new DOMParser().parseFromString(data.content, 'image/svg+xml');\n let svgnode = svgdoc.querySelector('svg');\n if (targetDiv) {\n targetDiv.replaceChildren(svgnode);\n }\n targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId + '-modal');\n if (targetDiv) {\n targetDiv.replaceChildren(svgnode);\n targetDiv.parentElement.previousElementSibling.outerHTML = data.completion;\n }\n return true;\n }).catch((error) => {\n Log.error(error);\n return false;\n });\n};\n"],"names":["selectors","LEARNINGMAP_RENDER_CONTAINER_PREFIX","cmId","inmodal","rendererPendingPromise","Pending","renderLearningmap","resolve","Ajax","call","methodname","args","then","data","targetDiv","document","getElementById","svgnode","DOMParser","parseFromString","content","querySelector","replaceChildren","parentElement","previousElementSibling","outerHTML","completion","catch","error"],"mappings":";;;;;;;;sPA2BaA,UAAY,CACrBC,oCAAqC,4EASrB,SAACC,UAAMC,sEACjBC,uBAAyB,IAAIC,iBAAQ,4BAA8BH,MACzEI,kBAAkBJ,KAAMC,SACxBC,uBAAuBG,iBAQdD,kBAAqBJ,OACbM,cAAKC,KAClB,CACI,CACIC,WAAY,kCACZC,KAAM,MACMT,SAKf,GAAGU,MAAKC,WACTC,UAAYC,SAASC,eAAehB,UAAUC,oCAAsCC,MAEpFe,SADS,IAAIC,WAAYC,gBAAgBN,KAAKO,QAAS,iBACtCC,cAAc,cAC/BP,WACAA,UAAUQ,gBAAgBL,SAE9BH,UAAYC,SAASC,eAAehB,UAAUC,oCAAsCC,KAAO,UACvFY,YACAA,UAAUQ,gBAAgBL,SAC1BH,UAAUS,cAAcC,uBAAuBC,UAAYZ,KAAKa,aAE7D,KACRC,OAAOC,qBACFA,MAAMA,QACH"} \ No newline at end of file diff --git a/amd/src/learningmap.js b/amd/src/learningmap.js index 52193ad..59a7390 100644 --- a/amd/src/learningmap.js +++ b/amd/src/learningmap.js @@ -1,6 +1,30 @@ -import {exception as displayException} from 'core/notification'; +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Main module for the learningmap editor + * + * @module mod_learningmap/learningmap + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +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'; const circleRadius = 10; @@ -16,7 +40,7 @@ const pathTypes = { quadraticbezier: 2, }; -export const init = () => { +export const init = async() => { // Load the needed template on startup for better execution speed. Templates.prefetchTemplates(['mod_learningmap/cssskeleton']); @@ -143,6 +167,12 @@ export const init = () => { hideContextMenu(); } }); + advancedSettingsIcon.addEventListener('keydown', function(e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + advancedSettingsIcon.click(); + } + }); let advancedSettingsClose = document.getElementById('learningmap-advanced-settings-close'); if (advancedSettingsClose) { advancedSettingsClose.addEventListener('click', function() { @@ -150,15 +180,16 @@ export const init = () => { }); } - advancedSettingsLogic('hidepaths', placestore.getHidePaths, placestore.setHidePaths); - advancedSettingsLogic('usecheckmark', placestore.getUseCheckmark, placestore.setUseCheckmark); - advancedSettingsLogic('hover', placestore.getHover, placestore.setHover); - advancedSettingsLogic('pulse', placestore.getPulse, placestore.setPulse); - advancedSettingsLogic('showall', placestore.getShowall, placestore.setShowall); - advancedSettingsLogic('hidestroke', placestore.getHideStroke, placestore.setHideStroke); - advancedSettingsLogic('showtext', placestore.getShowText, placestore.setShowText, fixPlaceLabels); - advancedSettingsLogic('slicemode', placestore.getSliceMode, placestore.setSliceMode); - advancedSettingsLogic('showwaygone', placestore.getShowWayGone, placestore.setShowWayGone); + await advancedSettingsLogic('hidepaths', placestore.getHidePaths, placestore.setHidePaths); + await advancedSettingsLogic('usecheckmark', placestore.getUseCheckmark, placestore.setUseCheckmark); + await advancedSettingsLogic('hover', placestore.getHover, placestore.setHover); + await advancedSettingsLogic('pulse', placestore.getPulse, placestore.setPulse); + await advancedSettingsLogic('showall', placestore.getShowall, placestore.setShowall); + await advancedSettingsLogic('hidestroke', placestore.getHideStroke, placestore.setHideStroke); + await advancedSettingsLogic('showtext', placestore.getShowText, placestore.setShowText, fixPlaceLabels); + await advancedSettingsLogic('slicemode', placestore.getSliceMode, placestore.setSliceMode); + await advancedSettingsLogic('showwaygone', placestore.getShowWayGone, placestore.setShowWayGone); + await advancedSettingsLogic('description', getTitleAndDesc, setTitleAndDesc); } // Attach listener to the color choosers @@ -665,6 +696,7 @@ export const init = () => { let 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); if (title !== null) { link.appendChild(title); @@ -675,6 +707,52 @@ export const init = () => { return link; } + /** + * Set the title and description of the svg document. + * @param {*} placestore + * @param {*} values + */ + function setTitleAndDesc(placestore, values) { + let titlenode = svgnode.querySelector('svg>title'); + if (!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'); + if (!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()); + } + + /** + * Get title and description of the svg document. + * @returns object + */ + function getTitleAndDesc() { + let title = ''; + let desc = ''; + let titlenode = svgnode.querySelector('svg>title'); + if (titlenode) { + title = titlenode.textContent; + } + let descnode = svgnode.querySelector('svg>desc'); + if (descnode) { + desc = descnode.textContent; + } + return { + title: title, + description: desc + }; + } + /** * Adds a place on the SVG map. This function also prepares the code for linking activities * and adding titles (for accessibility). @@ -782,7 +860,7 @@ export const init = () => { /** * Removes a place from the SVG and the placestore. This function also removes all - * touching paths and entries in statringplaces / targetplaces linking to the removed + * touching paths and entries in startingplaces / targetplaces linking to the removed * place. * @param {any} event event causing the remove order */ @@ -920,17 +998,58 @@ export const init = () => { * @param {*} setCall Method of placestore to call to save value * @param {*} callback Additional callback after value is saved */ - function advancedSettingsLogic(name, getCall, setCall, callback = null) { + async function advancedSettingsLogic(name, getCall, setCall, callback = null) { let settingItem = document.getElementById('learningmap-advanced-setting-' + name); if (settingItem) { - settingItem.checked = getCall.call(placestore); - settingItem.addEventListener('change', function() { - setCall.call(placestore, settingItem.checked); - if (callback !== null) { - callback(); - } - updateCSS(); - }); + // Settings that need a modal for editing (e.g. title and description) have a link to + // open the modal, other settings have a checkbox. + if (settingItem.nodeName == 'A') { + const descriptionHandler = async() => { + const values = getCall(); + try { + const strings = await Str.get_strings([ + {key: 'titleanddescription', component: 'mod_learningmap'}, + {key: 'save', component: 'core'}, + ]); + return saveCancel( + strings[0], + Templates.render('mod_learningmap/' + name + '-modal', values), + strings[1], + () => { + let values = {}; + let formentries = document.querySelectorAll('.mod_learningmap_' + name + '_value'); + formentries.forEach((element) => { + values[element.name] = element.value; + }); + setCall(placestore, values); + if (callback !== null) { + callback(); + } + updateCSS(); + } + ); + } catch (ex) { + displayException(ex); + return null; + } + }; + settingItem.addEventListener('click', descriptionHandler); + settingItem.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + descriptionHandler(); + } + }); + // Boolean settings can be directly changed with a checkbox in the advanced settings menu. + } else { + settingItem.checked = getCall.call(placestore); + settingItem.addEventListener('change', function() { + setCall.call(placestore, settingItem.checked); + if (callback !== null) { + callback(); + } + updateCSS(); + }); + } } } diff --git a/amd/src/linkmodal.js b/amd/src/linkmodal.js index 408fa97..4d35a77 100644 --- a/amd/src/linkmodal.js +++ b/amd/src/linkmodal.js @@ -18,7 +18,6 @@ import Ajax from 'core/ajax'; import * as manualcompletion from 'core_course/manual_completion_toggle'; import {renderLearningmap} from 'mod_learningmap/renderer'; import CourseEvents from 'core_course/events'; -import $ from 'jquery'; /** * Helper for opening course modules in a modal that do not have a view page. @@ -43,28 +42,61 @@ export const init = async(learningmapcmid, inmodal = false) => { event.preventDefault(); const cmid = target.getAttribute('data-cmid'); if (cmid) { - const data = await Ajax.call([{ - methodname: 'mod_learningmap_get_cm', - args: { - cmid: cmid - }, - }])[0]; - let js = $.parseHTML(data.js, null, true).map(node => node.innerHTML).join("\n"); - const modal = await Modal.create({ - title: data.name, - body: data.completion + data.html, - show: false, - removeOnClose: true, - large: true, - }); - modal.bodyJS = js; - modal.show(); - manualcompletion.init(); - document.addEventListener(CourseEvents.manualCompletionToggled, () => { - renderLearningmap(learningmapcmid); - }); + await openModal(event, learningmapcmid, inmodal); } } }); + container.addEventListener('keydown', async(event) => { + if (event.key === 'Enter') { + const target = event.target.closest('a[data-cmid]'); + if (target && !target.hasAttribute('xlink:href')) { + event.preventDefault(); + const cmid = target.getAttribute('data-cmid'); + if (cmid) { + await openModal(event, learningmapcmid, inmodal); + } + } + } + }); + } +}; + +/** + * Opens a modal for a course module link that does not have a view page. + * @param {Event} event - The click or keydown event. + * @param {number} learningmapcmid - The course module ID of the learning map. + * @param {boolean} inmodal - Whether the learning map is already in a modal. + */ +export const openModal = async(event, learningmapcmid, inmodal) => { + const target = event.target.closest('a[data-cmid]'); + if (target && !target.hasAttribute('xlink:href')) { + event.preventDefault(); + const cmid = target.getAttribute('data-cmid'); + if (cmid) { + const data = await Ajax.call([{ + methodname: 'mod_learningmap_get_cm', + args: { + cmid: cmid + }, + }])[0]; + const container = document.createElement('div'); + container.innerHTML = data.js; + let js = Array.from(container.querySelectorAll('script')) + .map(s => s.textContent || '') + .join('\n'); + const modal = await Modal.create({ + title: data.name, + body: data.completion + data.html, + show: false, + removeOnClose: true, + large: true, + }); + modal.bodyJS = js; + modal.show(); + manualcompletion.init(); + document.addEventListener(CourseEvents.manualCompletionToggled, () => { + renderLearningmap(learningmapcmid, inmodal); + }); + } } }; diff --git a/amd/src/renderer.js b/amd/src/renderer.js index dc07175..29739bd 100644 --- a/amd/src/renderer.js +++ b/amd/src/renderer.js @@ -59,12 +59,14 @@ export const renderLearningmap = (cmId) => { promises[0].then(data => { let targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId); + let svgdoc = new DOMParser().parseFromString(data.content, 'image/svg+xml'); + let svgnode = svgdoc.querySelector('svg'); if (targetDiv) { - targetDiv.innerHTML = data.content; + targetDiv.replaceChildren(svgnode); } targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId + '-modal'); if (targetDiv) { - targetDiv.innerHTML = data.content; + targetDiv.replaceChildren(svgnode); targetDiv.parentElement.previousElementSibling.outerHTML = data.completion; } return true; diff --git a/classes/external/get_cm.php b/classes/external/get_cm.php index dc468db..4a1878e 100644 --- a/classes/external/get_cm.php +++ b/classes/external/get_cm.php @@ -83,6 +83,7 @@ public static function execute(int $cmid): array { 'visible' => $cm->visible, 'groupmode' => groups_get_activity_groupmode($cm, $course), 'groupingid' => $cm->groupingid, + 'modname' => $cm->modname, ]; // Remove description for labels, because it is already in html. @@ -124,6 +125,7 @@ public static function execute_returns(): external_single_structure { 'html' => new external_value(PARAM_RAW, 'Course module html'), 'js' => new external_value(PARAM_RAW, 'Course module javascript'), 'completion' => new external_value(PARAM_RAW, 'Completion html'), + 'modname' => new external_value(PARAM_TEXT, 'Module name'), ] ); } diff --git a/classes/mapworker.php b/classes/mapworker.php index 2813213..2852262 100644 --- a/classes/mapworker.php +++ b/classes/mapworker.php @@ -79,15 +79,20 @@ public function __construct( ) { global $USER; $svgcode = preg_replace( - '/(?!(<\!\[CDATA\[))(.*)<\/text>/', + '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/text>/', '', $svgcode ); $svgcode = preg_replace( - '/(?!(<\!\[CDATA\[))(.*)<\/title>/', + '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/title>/', '', $svgcode ); + $svgcode = preg_replace( + '/]*)>(?!(<\!\[CDATA\[))(.*?)<\/desc>/', + '', + $svgcode + ); $this->edit = $edit; $placestore['editmode'] = $this->edit; $this->placestore = $placestore; @@ -140,7 +145,7 @@ public function process_map_objects(): void { $impossible = []; $allplaces = []; $links = []; - + $placenames = []; $modinfo = get_fast_modinfo($this->cm->get_course(), $USER->id); $allcms = array_keys($modinfo->get_cms()); @@ -178,15 +183,17 @@ public function process_map_objects(): void { } if (!$this->edit) { $this->svgmap->set_attribute($place['linkId'], 'data-cmid', $placecm->id); + $this->svgmap->set_attribute($place['linkId'], 'tabindex', '0'); $this->svgmap->remove_link($place['linkId']); if (!empty($url)) { $this->svgmap->set_link($place['linkId'], $url); } } $links[$place['id']] = $place['linkId']; + $placenames[$place['id']] = $placecm->get_formatted_name(); $this->svgmap->update_text_and_title( $place['id'], - $placecm->get_formatted_name(), + $placenames[$place['id']], // Add info to target places (for accessibility). in_array($place['id'], $this->placestore['targetplaces']) ? ' (' . get_string('targetplace', 'learningmap') . ')' : @@ -215,6 +222,13 @@ public function process_map_objects(): void { } if (!($this->edit)) { foreach ($this->placestore['paths'] as $path) { + $this->svgmap->update_title( + $path['id'], + get_string('pathtitle', 'mod_learningmap', [ + 'from' => $placenames[$path['sid']], + 'to' => $placenames[$path['fid']], + ]) + ); // If the beginning or the ending of the path is a completed place and this place is available, // show path and the place on the other end. if (in_array($path['sid'], $completedplaces) || in_array($path['fid'], $completedplaces)) { @@ -278,6 +292,12 @@ public function process_map_objects(): void { } } } + $this->svgmap->set_attribute('learningmap-svgmap-' . $this->placestore['mapid'], 'role', 'application'); + $this->svgmap->set_attribute( + 'learningmap-svgmap-' . $this->placestore['mapid'], + 'aria-labelledby', + 'title-' . $this->placestore['mapid'] . ' desc-' . $this->placestore['mapid'] + ); $this->svgmap->save_svg_data(); } @@ -303,6 +323,7 @@ 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(); } diff --git a/classes/svgmap.php b/classes/svgmap.php index 5885e3a..4dc4b3d 100644 --- a/classes/svgmap.php +++ b/classes/svgmap.php @@ -227,6 +227,32 @@ public function update_text_and_title(string $placeid, string $text, string $add } } + /** + * Updates the title element of a place or path. + * + * @param string $id Id of a place or path + * @param string $text Text to set the title element to + * @return void + */ + public function update_title(string $id, string $text): void { + $node = $this->get_element_by_id($id); + if ($node) { + $titlenode = null; + foreach ($node->childNodes as $child) { + if ($child->nodeName == 'title') { + $titlenode = $child; + break; + } + } + if ($titlenode) { + $titlenode->nodeValue = $text; + } else { + $titlenode = $this->dom->createElementNS('http://www.w3.org/2000/svg', 'title', $text); + $node->insertBefore($titlenode, $node->firstChild); + } + } + } + /** * Adds the learningmap-hidden class to a place or path. * @@ -508,4 +534,20 @@ public function set_attribute(string $id, string $attribute, string $value): voi $element->setAttribute($attribute, $value); } } + + /** + * Replaces all CDATA sections with properly escaped content + * + * @return void + */ + public function replace_cdata(): void { + $this->svgcode = preg_replace_callback( + '//s', + function ($matches) { + return htmlspecialchars($matches[1], ENT_XML1, 'UTF-8', false); + }, + $this->svgcode + ); + $this->load_dom(); + } } diff --git a/lang/en/learningmap.php b/lang/en/learningmap.php index 92fc1d5..e85c71c 100644 --- a/lang/en/learningmap.php +++ b/lang/en/learningmap.php @@ -34,6 +34,7 @@ $string['backlinkallowed'] = 'Allow automatic backlinks'; $string['backlinkallowed_desc'] = 'If this setting is enabled, users can choose to automatically set backlinks to the learning map from the module pages of the activities used in the learning map.'; $string['cachedef_backlinks'] = 'This cache stores information about whether there is a backlink to the learning map to show on a course module page.'; +$string['checkmark'] = 'Checkmark'; $string['completion_with_all_places'] = 'Reaching all places is necessary for completion'; $string['completion_with_all_targets'] = 'Reaching all target places is necessary for completion'; $string['completion_with_one_target'] = 'Reaching one target place is necessary for completion'; @@ -42,6 +43,8 @@ $string['completiondetail:one_target'] = 'Reach one target place'; $string['completiondisabled'] = 'Completion tracking is disabled in course settings. Without completion tracking this plugin won\'t work.'; $string['completiontype'] = 'Type of completion'; +$string['description'] = 'Description'; +$string['description_help'] = 'A description of the background image, mainly for use with screenreaders'; $string['editorhelp'] = 'How to use the editor'; $string['editplace'] = 'Edit place'; $string['fill_backlink_cache_task'] = 'Fill learningmap backlink cache'; @@ -87,6 +90,7 @@ $string['nocompletionenabled'] = 'Not available because completion is not enabled'; $string['ownprogress'] = 'My own progress'; $string['paths'] = 'Paths'; +$string['pathtitle'] = 'Path between "{$a->from}" and "{$a->to}"'; $string['places'] = 'Places'; $string['pluginadministration'] = 'Learning map administration'; $string['pluginname'] = 'Learning map'; @@ -108,6 +112,8 @@ $string['startingplace'] = 'Starting place'; $string['svgcode'] = 'SVG code'; $string['targetplace'] = 'Target place'; +$string['title'] = 'Title'; +$string['titleanddescription'] = 'Title and description'; $string['usecasehelp'] = 'How to use learning maps'; $string['usecaselink'] = 'Link to a page explaining the use of the learning map'; $string['usecheckmark'] = 'Checkmark for visited places'; diff --git a/lib.php b/lib.php index a6ee7fe..819d45b 100644 --- a/lib.php +++ b/lib.php @@ -42,6 +42,11 @@ 'showwaygone', ]); +/** + * Array with all features that do have non boolean values. + */ +define('LEARNINGMAP_EXTRA_FEATURES', ['description']); + /** * Adds a new learningmap instance * @@ -266,7 +271,7 @@ function learningmap_cm_info_view(cm_info $cm): void { $mapcontent = null; - if (helper::is_ajax_request()) { + if (helper::is_ajax_request() || \core_useragent::is_moodle_app()) { // If this is an ajax request to get the cm, we need to return only the map code. $mapcontent = learningmap_get_learningmap($cm); } @@ -361,12 +366,11 @@ function learningmap_get_learningmap(cm_info $cm): string { $filtermanager = filter_manager::instance(); $skipfilters = array_diff(array_keys(filter_get_active_in_context($cm->context)), $allowedfilters); + $code = $OUTPUT->render_from_template('mod_learningmap/mapcontainer', ['mapcode' => $worker->get_svgcode()]); + return( $filtermanager->filter_text( - $OUTPUT->render_from_template( - 'mod_learningmap/mapcontainer', - ['mapcode' => $worker->get_svgcode()] - ), + $code, $cm->context, ['trusted' => true, 'noclean' => true], $skipfilters diff --git a/mod_form.php b/mod_form.php index 5a5bc77..ad9c789 100644 --- a/mod_form.php +++ b/mod_form.php @@ -58,6 +58,10 @@ public function definition(): void { if ($CFG->branch >= 500 && !plugin_supports('mod', $module->modname, FEATURE_CAN_DISPLAY, true)) { continue; } + if (isset($this->_cm->id) && $this->_cm->id == $cmid) { + // Do not include the learningmap itself. + continue; + } // Get only course modules which are not deleted. if ($module->deletioninprogress == 0) { $s['coursemodules'][] = [ @@ -89,12 +93,13 @@ public function definition(): void { ); $features = []; - foreach (LEARNINGMAP_FEATURES as $feature) { + foreach (array_merge(LEARNINGMAP_FEATURES, LEARNINGMAP_EXTRA_FEATURES) as $feature) { $features[] = [ 'name' => $feature, 'title' => get_string($feature, 'learningmap'), 'text' => get_string($feature . '_help', 'learningmap'), 'alt' => get_string('help'), + 'boolsetting' => in_array($feature, LEARNINGMAP_FEATURES), ]; } $mform->addElement( diff --git a/styles.css b/styles.css index ad1a2fa..7dc7653 100644 --- a/styles.css +++ b/styles.css @@ -146,3 +146,15 @@ .learningmap-container { width: 100%; } + +.learningmap_description_form { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.learningmap_description_form input, +.learningmap_description_form textarea { + width: 100%; + box-sizing: border-box; +} diff --git a/templates/advancedsettings.mustache b/templates/advancedsettings.mustache index 01f4f29..b181565 100644 --- a/templates/advancedsettings.mustache +++ b/templates/advancedsettings.mustache @@ -57,13 +57,20 @@
{{#features}} -
+
- -
-
- - {{>core/help_icon}} + +
+
+ {{#boolsetting}} + + {{/boolsetting}} + {{^boolsetting}} + + {{#pix}}t/edit, core, {{#str}} edit, core{{/str}}{{/pix}} + + {{/boolsetting}} + {{>core/help_icon}}
{{/features}} diff --git a/templates/cssskeleton.mustache b/templates/cssskeleton.mustache index 405a903..cfbcb12 100644 --- a/templates/cssskeleton.mustache +++ b/templates/cssskeleton.mustache @@ -64,6 +64,12 @@ {{/editmode}} } +#learningmap-svgmap-{{mapid}} a:focus-visible, +#learningmap-svgmap-{{mapid}} path:focus-visible { + outline: 2px solid #0000ff; + outline-offset: 2px; +} + #learningmap-svgmap-{{mapid}} .learningmap-place.learningmap-visited { visibility: visible; opacity: 1; diff --git a/templates/description-modal.mustache b/templates/description-modal.mustache new file mode 100644 index 0000000..9585268 --- /dev/null +++ b/templates/description-modal.mustache @@ -0,0 +1,37 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_learningmap/description-modal + + Editing modal for the SVG description + + Example context (json): + { + "title": "Shorter text", + "description": "Some description text" + } +}} +
+
+ + +
+
+ + +
+
diff --git a/templates/formitem.mustache b/templates/formitem.mustache index bad05f2..c738817 100644 --- a/templates/formitem.mustache +++ b/templates/formitem.mustache @@ -100,7 +100,7 @@
- +
diff --git a/templates/svgdefs.mustache b/templates/svgdefs.mustache index 4f1a749..9cde51c 100644 --- a/templates/svgdefs.mustache +++ b/templates/svgdefs.mustache @@ -33,7 +33,9 @@ } }} - + + {{#str}} checkmark, mod_learningmap{{/str}} + diff --git a/templates/svgskeleton.mustache b/templates/svgskeleton.mustache index 5c6ac9f..d20c178 100644 --- a/templates/svgskeleton.mustache +++ b/templates/svgskeleton.mustache @@ -47,10 +47,13 @@ "hidepaths":false, "mapid":"61e7dad7afb67", "usecheckmark":false, - "editmode":true + "editmode":true, + "description": "Description of the background image" } }} - + + + {{> mod_learningmap/svgdefs }} {{> mod_learningmap/cssskeleton }}