Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions amd/build/learningmap.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/learningmap.min.js.map

Large diffs are not rendered by default.

69 changes: 50 additions & 19 deletions amd/src/learningmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
* Main module for the learningmap editor
*
* @module mod_learningmap/learningmap
* @copyright 2025 ISB Bayern
* @copyright 2021-2026 ISB Bayern
* @author Stefan Hanauska <stefan.hanauska@csg-in.de>
* @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';
import {debounce} from 'core/utils';

const circleRadius = 10;

Expand Down Expand Up @@ -108,13 +109,13 @@ export const init = async() => {
if (activitySelector.value) {
let text = document.getElementById('text' + elementForActivitySelector);
if (text) {
text.replaceChildren(svgdoc.createCDATASection(
text.replaceChildren(svgdoc.createTextNode(
activitySelector.querySelector('option[value="' + activitySelector.value + '"]').textContent
));
}
let title = document.getElementById('title' + elementForActivitySelector);
if (title) {
title.replaceChildren(svgdoc.createCDATASection(
title.replaceChildren(svgdoc.createTextNode(
activitySelector.querySelector('option[value="' + activitySelector.value + '"]').textContent
));
}
Expand Down Expand Up @@ -306,11 +307,11 @@ export const init = async() => {
dragel = el;
if (el) {
el.addEventListener('mousedown', startDrag);
el.addEventListener('mousemove', drag);
el.addEventListener('mousemove', debounce(drag, 5));
el.addEventListener('mouseup', endDrag);
el.addEventListener('mouseleave', endDrag);
el.addEventListener('touchstart', startTouch);
el.addEventListener('touchmove', drag);
el.addEventListener('touchmove', debounce(drag, 5));
el.addEventListener('touchend', endTouch);
el.addEventListener('touchleave', endTouch);
el.addEventListener('touchcancel', endTouch);
Expand All @@ -336,7 +337,7 @@ export const init = async() => {
pathsToUpdateSecondPoint = placestore.getPathsWithSid(selectedElement.id);
} else if (evt.target.nodeName == 'text') {
selectedElement = evt.target;
let place = selectedElement.parentNode.querySelector('.learningmap-place');
let place = findPlaceForText(selectedElement.id);
offset = getMousePosition(evt);
offset.x -= parseInt(selectedElement.getAttributeNS(null, "dx")) + place.cx.baseVal.value;
offset.y -= parseInt(selectedElement.getAttributeNS(null, "dy")) + place.cy.baseVal.value;
Expand Down Expand Up @@ -365,7 +366,7 @@ export const init = async() => {
let cx = coord.x - offset.x;
let cy = coord.y - offset.y;
if (selectedElement.nodeName == 'text') {
let place = selectedElement.parentNode.querySelector('.learningmap-place');
let place = findPlaceForText(selectedElement.id);
// Calculate the delta from the current mouse position to the corresponding place.
// coord: current mouse position
// offset: delta from the mouse position to the coordinates of the text node
Expand Down Expand Up @@ -641,7 +642,7 @@ export const init = async() => {
// Default value for delta: Circle radius * 1.5 (as a padding)
text.setAttribute('dx', circleRadius * 1.5);
text.setAttribute('dy', circleRadius * 1.5);
let textcontent = svgdoc.createCDATASection(content);
let textcontent = svgdoc.createTextNode(content);
text.replaceChildren(textcontent);
return text;
}
Expand Down Expand Up @@ -689,10 +690,9 @@ export const init = async() => {
* @param {*} child child item to set the link on
* @param {*} id id of the link
* @param {*} title title of the link
* @param {*} text text to describe the link
* @returns {any}
*/
function link(child, id, title = null, text = null) {
function link(child, id, title = null) {
let link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
link.setAttribute('id', id);
link.setAttribute('xlink:href', '');
Expand All @@ -701,9 +701,6 @@ export const init = async() => {
if (title !== null) {
link.appendChild(title);
}
if (text !== null) {
link.appendChild(text);
}
return link;
}

Expand All @@ -718,7 +715,7 @@ export const init = async() => {
titlenode = document.createElementNS('http://www.w3.org/2000/svg', 'title');
svgnode.appendChild(titlenode);
}
let titlecontent = svgdoc.createCDATASection(values.title);
let titlecontent = svgdoc.createTextNode(values.title);
titlenode.replaceChildren(titlecontent);
titlenode.setAttribute('id', 'title-' + placestore.getMapid());

Expand All @@ -727,7 +724,7 @@ export const init = async() => {
descnode = document.createElementNS('http://www.w3.org/2000/svg', 'desc');
svgnode.appendChild(descnode);
}
let desccontent = svgdoc.createCDATASection(values.description);
let desccontent = svgdoc.createTextNode(values.description);
descnode.replaceChildren(desccontent);
descnode.setAttribute('id', 'desc-' + placestore.getMapid());
}
Expand Down Expand Up @@ -760,6 +757,15 @@ export const init = async() => {
*/
function addPlace(event) {
let placesgroup = document.getElementById('placesGroup');
if (!placesgroup) {
placesgroup = document.getElementById('placesGroup-' + placestore.getMapid());
}
let textgroup = document.getElementById('textsGroup-' + placestore.getMapid());
if (!textgroup) {
textgroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
textgroup.setAttribute('id', 'textsGroup-' + placestore.getMapid());
svgnode.appendChild(textgroup);
}
let placeId = 'p' + placestore.getId();
let linkId = 'a' + placestore.getId();
var CTM = event.target.getScreenCTM();
Expand All @@ -772,10 +778,12 @@ export const init = async() => {
link(
circle(cx, cy, circleRadius, 'learningmap-place learningmap-draggable learningmap-emptyplace', placeId),
linkId,
title('title' + placeId),
text('text' + placeId, '', cx, cy)
title('title' + placeId)
)
);
textgroup.appendChild(
text('text' + placeId, '', cx, cy)
);
placestore.addPlace(placeId, linkId);
}

Expand Down Expand Up @@ -840,6 +848,9 @@ export const init = async() => {
let pid = 'p' + fid + '_' + sid;
if (document.getElementById(pid) === null) {
let pathsgroup = document.getElementById('pathsGroup');
if (!pathsgroup) {
pathsgroup = document.getElementById('pathsGroup-' + placestore.getMapid());
}
let first = document.getElementById('p' + fid);
let second = document.getElementById('p' + sid);
if (pathsgroup && first && second) {
Expand Down Expand Up @@ -871,7 +882,10 @@ export const init = async() => {
placestore.removePlace(event.target.id);
parent.removeChild(place);
parent.parentNode.removeChild(parent);

let textNode = document.getElementById('text' + event.target.id);
if (textNode) {
textNode.parentNode.removeChild(textNode);
}
updateCode();
}

Expand Down Expand Up @@ -906,6 +920,9 @@ export const init = async() => {
let previewimage = document.getElementsByClassName('realpreview');
if (previewimage.length > 0) {
let background = document.getElementById('learningmap-background-image');
if (!background) {
background = document.getElementById('learningmap-background-image-' + placestore.getMapid());
}
let backgroundurl = previewimage[0].getAttribute('src').split('?')[0];
// If the uploaded file reuses the filename of a previously uploaded image, they differ
// only in the oid. So one has to append the oid to the url.
Expand All @@ -922,6 +939,9 @@ export const init = async() => {
*/
function registerBackgroundListener() {
let background = document.getElementById('learningmap-background-image');
if (!background) {
background = document.getElementById('learningmap-background-image-' + placestore.getMapid());
}
if (background) {
background.addEventListener('load', function() {
background.removeAttribute('height');
Expand Down Expand Up @@ -1069,8 +1089,9 @@ export const init = async() => {
}
}
let placeNode = document.getElementById(place.id);
let textGroup = document.getElementById('textsGroup-' + placestore.getMapid());
let textNode = text('text' + place.id, content, placeNode.cx.baseVal.value, placeNode.cy.baseVal.value);
placeNode.parentNode.appendChild(textNode);
textGroup.appendChild(textNode);
}
}
}
Expand All @@ -1082,4 +1103,14 @@ export const init = async() => {
let advancedSettings = document.getElementById('learningmap-advanced-settings');
advancedSettings.setAttribute('hidden', '');
}

/**
* Returns the place that belongs to the given text id.
* @param {*} textId
* @returns {*} The place element
*/
function findPlaceForText(textId) {
let placename = textId.replace('text', '');
return svgnode.getElementById(placename);
}
};
8 changes: 5 additions & 3 deletions backup/moodle2/restore_learningmap_activity_task.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ public function after_restore(): void {
$newmapid = uniqid();
$placestore->mapid = $newmapid;

if (!isset($placestore->version) || $placestore->version < 2024072201) {
$placestore->version = 2024072201;
if (!isset($placestore->version) || $placestore->version < 2026022200) {
$placestore->version = 2026022200;
// Needs 1 as default value (otherwise all place strokes would be hidden).
if (!isset($placestore->strokeopacity)) {
$placestore->strokeopacity = 1;
Expand All @@ -121,9 +121,11 @@ public function after_restore(): void {
$mapworker = new \mod_learningmap\mapworker($mapcode, (array)$placestore);
$mapworker->replace_stylesheet();
$mapworker->replace_defs();
$mapworker->fix_svg();
$item->svgcode = $mapworker->get_svgcode();
}
$item->svgcode = str_replace('learningmap-svgmap-' . $oldmapid, 'learningmap-svgmap-' . $newmapid, $item->svgcode);
// Map ids are used in the svg code as well, so we need to replace them there too.
$item->svgcode = str_replace('-' . $oldmapid, '-' . $newmapid, $item->svgcode);
$item->placestore = json_encode($placestore);
$item->course = $courseid;
$DB->update_record('learningmap', $item);
Expand Down
3 changes: 2 additions & 1 deletion backup/moodle2/restore_learningmap_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ protected function define_structure(): array {
}

/**
* Restore a learningmap record.
* Restore a learningmap record. This will not change placestore and
* svgcode as they will be processed in the after_restore method.
* @param array|object $data
* @throws base_step_exception
* @throws dml_exception
Expand Down
30 changes: 12 additions & 18 deletions classes/mapworker.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,6 @@ public function __construct(
int $group = 0
) {
global $USER;
$svgcode = preg_replace('/(<\!--\[CDATA\[)(.*?)(\]\]-->)/', '<![CDATA[$2]]>', $svgcode);
$svgcode = preg_replace(
'/<text([^>]*)>(?!(<\!\[CDATA\[))(.*?)<\/text>/',
'<text$1><![CDATA[$3]]></text>',
$svgcode
);
$svgcode = preg_replace(
'/<title([^>]*)>(?!(<\!\[CDATA\[))(.*?)<\/title>/',
'<title$1><![CDATA[$3]]></title>',
$svgcode
);
$svgcode = preg_replace(
'/<desc([^>]*)>(?!(<\!\[CDATA\[))(.*?)<\/desc>/',
'<desc$1><![CDATA[$3]]></desc>',
$svgcode
);
$this->edit = $edit;
$placestore['editmode'] = $this->edit;
$this->placestore = $placestore;
Expand Down Expand Up @@ -122,7 +106,7 @@ public function replace_stylesheet(array $placestoreoverride = []): void {
* @return void
*/
public function replace_defs(): void {
$this->svgmap->replace_defs();
$this->svgmap->replace_defs(['mapid' => $this->placestore['mapid']]);
}

/**
Expand Down Expand Up @@ -265,6 +249,7 @@ public function process_map_objects(): void {
} else {
$this->svgmap->set_hidden($links[$place]);
$this->svgmap->remove_link($links[$place]);
$this->svgmap->remove_attribute($links[$place], 'data-cmid');
}
}
// Remove all places that are impossible to reach.
Expand Down Expand Up @@ -324,7 +309,6 @@ public function is_path_between(string $place1, string $place2): ?string {
* @return string
*/
public function get_svgcode(): string {
$this->svgmap->replace_cdata();
return $this->svgmap->get_svgcode();
}

Expand All @@ -346,4 +330,14 @@ public function get_attribute(string $id, string $attribute): ?string {
public function get_active(): array {
return $this->active;
}

/**
* Helper function for upgrade. Fixes the SVG code of existing maps to ensure that the groups for paths,
* places and background are present and have the correct attributes.
*
* @return void
*/
public function fix_svg(): void {
$this->svgmap->fix_svg();
}
}
Loading
Loading