function MapEditor(id) { this.id = id; this.shiftKeyPressed = false; this.beingDragged = false; this.allowDeselect = true; this.colors = ['#1E90FF', '#FF1493', '#4B0082', '#32CD32', '#FF8C00', '#000000']; this.selectedColor; this.colorButtons = {}; this.thicknessValue = 1; this.opacityValue = 0.4; this.overlays = []; this.selectedShapes = []; this.listenersArray = []; this.mapsArray = []; this.drawingManager; this.selectedShape; this.savedOverlays = null; this.map = null; this.counter = 0; this.branchNum = 1; this.mouseState = "up"; this.thicknessRangeListener = this.thicknessValue; // ???? this.resizable = false; this.dontResize = false; this.polyOptions = { fillColor: '#CA4A2F', strokeWeight: this.thicknessValue, fillOpacity: this.opacityValue, editable: true, geodesic: false, draggable: true }; this.mapEditorHistory = new MapEditorHistory(this); } MapEditor.prototype.initMapEditorControls = function () { var that = this; var draggableCB = document.querySelector("input[name=draggableCB]"); draggableCB.addEventListener('change', function () { if (this.checked) { for (var i = 0; i < that.overlays.length; i++) { console.log("checked"); that.overlays[i].draggable = false; that.polyOptions.draggable = false; } } else { for (var i = 0; i < that.overlays.length; i++) { console.log("not checked"); that.overlays[i].draggable = true; that.polyOptions.draggable = true; } } }); //Update thickness var thicknessSlider = document.getElementById("thicknessRange" + "-" + this.id); var thicknessSliderOutput = document.getElementById("thicknessRangeVal" + "-" + this.id); thicknessSliderOutput.innerHTML = ((thicknessSlider.value / 20) * 100) / 100; thicknessSlider.oninput = function () { that.shapeSpecsChangeMD(); thicknessSliderOutput.innerHTML = ((this.value / 20) * 100) / 100; that.thicknessValue = this.value / 20; that.polyOptions.strokeWeight = that.thicknessValue; that.setSelectedThickness(that.thicknessValue); } //Update opacity var opacitySlider = document.getElementById("colourOpacity" + "-" + this.id); var opacitySliderOutput = document.getElementById("opacityRangeVal" + "-" + this.id); opacitySliderOutput.innerHTML = "% " + Math.round(opacitySlider.value); opacitySlider.oninput = function () { that.shapeSpecsChangeMD(); opacitySliderOutput.innerHTML = "% " + Math.round(this.value * 100) / 100; that.opacityValue = this.value / 100; that.polyOptions.fillOpacity = that.opacityValue; that.setSelectedOpacity(that.opacityValue); } document.getElementById("color-palette1" + "-" + this.id).addEventListener("mousedown", function() { that.shapeSpecsChangeMD() }); document.getElementById("thicknessRange" + "-" + this.id).addEventListener("mouseup", function () { that.shapeSpecsChangeMU() }); document.getElementById("colourOpacity" + "-" + this.id).addEventListener("mouseup", function() { that.shapeSpecsChangeMU() } ); document.onmousedown = function (ev) { that.mouseState = "down"; // console.log('Down State you can now start dragging'); //do not write any code here in this function } document.onmouseup = function (ev) { that.mouseState = "up"; // console.log('up state you cannot drag now because you are not holding your mouse') //do not write any code here in this function } //prompts the user to save the changes before reloading/leaving the page window.onbeforeunload = function (e) { var currentOverlays = JSON.stringify(ShapesUtil.overlayToJSON(that.overlays)); var enableMessage = currentOverlays !== that.savedOverlays; var message = "Changes are not saved. Are you sure you want to leave?"; console.log("@@@ onbeforeunload. currentOverlays: " + currentOverlays); console.log(that.savedOverlays); console.log(enableMessage); e = e || window.event; // For IE and Firefox if (e) { if(enableMessage){ if(currentOverlays !== "[]") { alert(message); e.returnValue = message; // For Safari return message; } } } } } MapEditor.prototype.settingThePath = function () { var that = this; this.listenersArray = [] this.counter = 0; this.branchNum = 1; for (var i = 0; i < this.selectedShapes.length * 2; i++) { for (var j = 1; j < 6; j++) { var path = "//*[@id='map-" + this.id + "']/div/div/div[1]/div[3]/div/div[3]/div[" + this.branchNum + "]/div[" + j + "]/div"; this.listenersArray[this.counter] = this.getElementByXpath(path); if (this.listenersArray[this.counter] !== (undefined || null)) { this.listenersArray[this.counter].addEventListener("mousemove", function () { that.resizable = true; that.shapeResize(); }); this.listenersArray[this.counter].addEventListener("mouseout", function () { if (this.mouseDown) { that.resizable = true; that.shapeResize(); } }); } this.counter++; } this.branchNum++; } } MapEditor.prototype.shapeResize = function () { if (this.mouseState == "down") { if (this.selectedShapes.length > 0) { if (this.resizable) { if (this.dontResize == false) { this.mapEditorHistory.historyOverlayPush(); } } } } } MapEditor.prototype.shapeSpecsChangeMD = function () { if (this.selectedShapes.length > 0) { this.mapEditorHistory.historyOverlayPush(); } } MapEditor.prototype.shapeSpecsChangeMU = function () { if (this.selectedShapes.length > 0) { this.mapEditorHistory.presentOverlayPush(); } } MapEditor.prototype.makeColorButton = function (color) { var that = this; var button = document.createElement('span'); button.className = 'color-buttons1'; button.style.backgroundColor = color; google.maps.event.addDomListener(button, 'click', function () { that.selectColor(color); that.setSelectedShapeColor(color); that.shapeSpecsChangeMU(); }); return button; } MapEditor.prototype.buildColorPalette = function () { var colorPalette = document.getElementById("color-palette1" + "-" + this.id); for (var i = 0; i < this.colors.length; ++i) { var currColor = this.colors[i]; var colorButton = this.makeColorButton(currColor); colorPalette.appendChild(colorButton); this.colorButtons[currColor] = colorButton; } this.selectColor(this.colors[0]); }; MapEditor.prototype.selectColor = function (color) { this.selectedColor = color; for (var i = 0; i < this.colors.length; ++i) { var currColor = this.colors[i]; this.colorButtons[currColor].style.border = currColor == color ? '2px solid #789' : '2px solid #fff'; } // Retrieves the current options from the drawing manager and replaces the // stroke or fill color as appropriate. var polylineOptions = this.drawingManager.get('polylineOptions'); polylineOptions.strokeColor = color; this.drawingManager.set('polylineOptions', polylineOptions); var rectangleOptions = this.drawingManager.get('rectangleOptions'); rectangleOptions.fillColor = color; this.drawingManager.set('rectangleOptions', rectangleOptions); var circleOptions = this.drawingManager.get('circleOptions'); circleOptions.fillColor = color; this.drawingManager.set('circleOptions', circleOptions); var polygonOptions = this.drawingManager.get('polygonOptions'); polygonOptions.fillColor = color; this.drawingManager.set('polygonOptions', polygonOptions); } MapEditor.prototype.initMapEditor = function () { var that = this; this.map = new google.maps.Map(document.getElementById("map-" + this.id), { center: { lat: -37.7891, lng: 175.3180 }, zoom: 14, mapId: this.id, }); this.mapsArray.push(this.map); // Add a style-selector control to the map. var styleControl = document.getElementById('style-selector-control' + "-" + this.id); this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(styleControl); // Set the map's style to the initial value of the selector. var styleSelector = document.getElementById('style-selector' + "-" + this.id); //console.log(styleSelector); //map = google.maps.Map(document.getElementById('map' +"-"+ this.id)); this.map.setOptions({ styles: styles[styleSelector.value] }); // Apply new JSON when the user selects a different style. styleSelector.addEventListener('change', function () { that.map.setOptions({ styles: styles[styleSelector.value] }); }); this.drawingManager = new google.maps.drawing.DrawingManager({ drawingMode: google.maps.drawing.OverlayType.RECTANGLE, drawingControl: true, drawingControlOptions: { position: google.maps.ControlPosition.TOP_CENTER, drawingModes: ['marker', 'circle', 'polygon', 'polyline', 'rectangle'] }, markerOptions: { draggable: true }, circleOptions: this.polyOptions, polylineOptions: this.polyOptions, polygonOptions: this.polyOptions, rectangleOptions: this.polyOptions, }); this.drawingManager.setMap(this.map); google.maps.event.addListener(this.drawingManager, "drawingmode_changed", function () { if (that.shiftKeyPressed != true && that.drawingManager.drawingMode !== null) { that.deselectAll(); } that.settingThePath(); }) // store reference to added overlay google.maps.event.addListener(this.drawingManager, 'overlaycomplete', function (e) { that.allowDeselect = true; that.mapEditorHistory.historyOverlayPush(); that.overlays.push(e.overlay); // store reference to added overlay var newShape = e.overlay; newShape.type = e.type; that.mapEditorHistory.presentOverlayPush(); if (e.type !== google.maps.drawing.OverlayType.MARKER) { that.addShapeListeners(newShape, e); that.setSelection(newShape, e); } else { that.addMarkerListeners(newShape, e); that.setSelection(newShape, e); } }); //Clears selection if clicked on the map when shift is not presseed google.maps.event.addListener(this.map, 'click', function (e) { var c = document.body.childNodes; if (e.target && e.target.matches("a.classA")) { console.log("Anchor element clicked!"); } if (that.shiftKeyPressed == false) { that.clearSelection(); that.selectedShape = null; } }); google.maps.event.addListener(this.map, 'mousedown', function (e) { that.dontResize = true; }); google.maps.event.addListener(this.map, 'mouseup', function (e) { that.dontResize = false; }); //Keyboard shortcuts var mapAndControls = document.getElementById("map-and-controls-" + this.id); console.log("@@@@ initMapEditor. mapAndControls: " + mapAndControls); //Sets shift as unpressed mapAndControls.addEventListener('keyup', function (event) { if (event.keyCode == '16') { that.shiftKeyPressed = false; } }); mapAndControls.addEventListener('keydown', function (event) { // https://stackoverflow.com/questions/2220196/how-to-decode-character-pressed-from-jquerys-keydowns-event-handler var keyCode = String.fromCharCode(event.which); // console.log("Key pressed: " + keyCode); //Sets shift as pressed if (event.keyCode == '16') { that.shiftKeyPressed = true; } else if (keyCode == 'Y' && (event.ctrlKey || event.metaKey)) { that.mapEditorHistory.redo(); } else if (keyCode == 'Z' && (event.ctrlKey || event.metaKey) ) { if (that.shiftKeyPressed == false) { that.mapEditorHistory.undo(); } } else if (keyCode == 'A' && (event.ctrlKey || event.metaKey)) { event.preventDefault(); that.drawingManager.setDrawingMode(null); that.selectAll(); } else if (keyCode == 'D' && (event.ctrlKey || event.metaKey)) { event.preventDefault(); that.deselectAll(); } else if (keyCode == '0' || keyCode == 'À' || keyCode == 'G') { that.drawingManager.setDrawingMode(null); } else if (keyCode == '1') { that.drawingManager.setDrawingMode('marker'); } else if (keyCode == '2') { that.drawingManager.setDrawingMode('circle'); } else if (keyCode == '3') { that.drawingManager.setDrawingMode('polygon'); } else if (keyCode == '4') { that.drawingManager.setDrawingMode('polyline'); } else if (keyCode == '5') { that.drawingManager.setDrawingMode('rectangle'); } else if (keyCode == 'S') { that.saveToArchives(); } else if (keyCode == 'Q') { that.printHistory(); } // console.log(keyCode); }); this.buildColorPalette(); var collection = gs.cgiParams.c; var site_name = gs.xsltParams.site_name; var nodeID = this.id; // documentID, hopefully contains section ID too var metaname = "GPS.mapOverlay"; // collection, site, documentID, metadataName, metadataPosition, responseFunction gs.functions.getArchivesMetadata(collection, site_name, nodeID, metaname, 0, function(responseText){ // responseText is of type GSMetadata // called when data has been retrieved from archives var JSONString = responseText.getValue(); if(JSONString !== "") { that.LOAD(JSONString, nodeID); that.savedOverlays = JSONString; } } ); // TODO: responseFunction in setMeta call } //Deletes a vertex if clicked on it MapEditor.prototype.vertexAndPolyDel = function (e, newShape) { var vertex = e.vertex; if (e.vertex !== undefined) { if (newShape.type === google.maps.drawing.OverlayType.POLYGON) { var path = newShape.getPaths().getAt(e.path); path.removeAt(e.vertex); if (path.length < 3) { newShape.setMap(null); } } if (newShape.type === google.maps.drawing.OverlayType.POLYLINE) { var path = newShape.getPath(); path.removeAt(e.vertex); if (path.length < 2) { newShape.setMap(null); } } } } MapEditor.prototype.addMarkerListeners = function (newShape, e) { var that = this; //cLICK EVENT IF A MARKER IS CREATED google.maps.event.addListener(newShape, 'click', function (e) { if(that.shiftKeyPressed){ } else { that.mapEditorHistory.historyOverlayPush(); newShape.setMap(null); that.mapEditorHistory.presentOverlayPush(); } }); google.maps.event.addListener(newShape, 'dragstart', function (e) { that.beingDragged = true; that.mapEditorHistory.historyOverlayPush(); }); google.maps.event.addListener(newShape, 'dragend', function () { that.beingDragged = false; that.mapEditorHistory.presentOverlayPush(); that.allowDeselect = false; }); } MapEditor.prototype.addShapeListeners = function (newShape, e) { var that = this; // Add an event listener that selects the newly-drawn shape when the user // mouses down on it. google.maps.event.addListener(newShape, 'click', function (e) { that.vertexAndPolyDel(e, newShape); }); google.maps.event.addListener(newShape, 'dragstart', function (e) { that.allowDeselect = false; that.mapEditorHistory.historyOverlayPush(); }); google.maps.event.addListener(newShape, 'dragend', function () { that.beingDragged = false; that.mapEditorHistory.presentOverlayPush(); that.settingThePath(); that.allowDeselect = false; that.setSelection(newShape, e); }); //Store information after the event ends google.maps.event.addListener(newShape, 'bounds_changed', function (e) { if (that.beingDragged == false) { that.mapEditorHistory.presentOverlayPush(); } }); //Add an event listener to select a shape if the mouse hovers over it google.maps.event.addListener(newShape, 'mousedown', function (e) { if (e.target && e.target.matches("a.classA")) { console.log("Anchor element clicked!"); } if (e.vertex !== undefined || e.edge !== undefined) { that.mapEditorHistory.historyOverlayPush() } if (that.drawingManager.drawingMode == null) { that.setSelection(newShape, e); } }); google.maps.event.addListener(newShape, 'mouseup', function (e) { if (e.vertex !== undefined || e.edge !== undefined) { that.mapEditorHistory.presentOverlayPush() } else { //that.setSelection(newShape, e); } }); } MapEditor.prototype.clearSelection = function () { if (this.selectedShape) { if (this.shiftKeyPressed == false) { for (var i = 0; i < this.selectedShapes.length; i++) { if(this.selectedShapes[i].type !== 'marker') { this.selectedShapes[i].setEditable(false); } } this.selectedShapes = []; } this.selectedShape = null; } } //Set selection for the selected overlay MapEditor.prototype.setSelection = function (shape, e) { //var that = this; if (shape.type !== 'marker') { if (this.shiftKeyPressed == false) { if (e !== null) { if (e.vertex == undefined) { if (e.edge == undefined) { this.clearSelection(); shape.setEditable(true); } } } } if (this.selectedShapes.includes(shape)) { if (e !== null) { if (e.vertex == undefined) { if (e.edge == undefined) { this.allowDeselect = true; this.removeFromSelectedShapes(shape); } } } } else { this.allowDeselect = false; shape.setEditable(true); this.selectedShapes.push(shape); } //Send the values to be updated var thi = shape.strokeWeight; var opa = shape.fillOpacity; var fCol = shape.fillColor; var sCol = shape.strokeColor; this.updateMenuValues(thi, opa, fCol, sCol); } else if (shape.type == 'marker') { this.allowDeselect = false; this.selectedShapes.push(shape); } this.selectedShape = shape; this.settingThePath(); } MapEditor.prototype.removeFromSelectedShapes = function (shape) { if (this.selectedShapes.includes(shape)) { if (this.allowDeselect) { const index = this.selectedShapes.indexOf(shape); this.selectedShapes.splice(index, 1); shape.setEditable(false); } this.allowDeselect = true; } } //Set selected thickness MapEditor.prototype.setSelectedThickness = function (sWeight) { if (this.selectedShapes.length > 0) { for (var i = 0; i < this.selectedShapes.length; i++) { this.selectedShapes[i].set('strokeWeight', sWeight); } } } //Set selected opacity MapEditor.prototype.setSelectedOpacity = function (fOpacity) { if (this.selectedShapes.length > 0) { for (var i = 0; i < this.selectedShapes.length; i++) { this.selectedShapes[i].set('fillOpacity', fOpacity); } } } //set selected fill colour MapEditor.prototype.setSelectedShapeColor = function (color) { if (this.selectedShapes.length > 0) { for (var i = 0; i < this.selectedShapes.length; i++) { this.selectedShapes[i].set('fillColor', color); this.selectedShapes[i].set('strokeColor', color); } } } MapEditor.prototype.getElementByXpath = function (path) { return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } MapEditor.prototype.updateMenuValues = function (thi, opa, fCol, sCol) { //Update thickness slider and value on the settings menu var thicknessSliderOutput = document.getElementById("thicknessRangeVal" + "-" + this.id); thicknessSliderOutput.innerHTML = thi; document.getElementById("thicknessRange" + "-" + this.id).value = Math.round((thi * 20) * 100) / 100; //Update the opacity slider and value on the settings menu var opacitySliderOutput = document.getElementById("opacityRangeVal" + "-" + this.id); opacitySliderOutput.innerHTML = "% " + Math.round(opa * 100) * 100 / 100; document.getElementById("colourOpacity" + "-" + this.id).value = (opa * 100) * 100 / 100; if (this.drawingManager.drawingMode == null) { this.selectColor(fCol); } } MapEditor.prototype.selectAll = function () { this.shiftKeyPressed = true; var e = new Object(); e.vertex = undefined; this.selectedShapes = []; for (var i = 0; i < this.overlays.length; i++) { this.setSelection(this.overlays[i], e); } this.shiftKeyPressed = false; } MapEditor.prototype.deselectAll = function () { for (var i = 0; i < this.selectedShapes.length; i++) { if (this.selectedShapes[i].type !== google.maps.drawing.OverlayType.MARKER) { this.selectedShapes[i].setEditable(false); } } this.selectedShapes = []; } // event handler for s being pressed *when map editor has the focus* // For saving and rebuilding, see map_scripts MapEditor.prototype.saveToArchives = function () { var that = this; console.log("Save pressed"); var json_overlays = ShapesUtil.overlayToJSON(this.overlays); var collection = gs.cgiParams.c; var site_name = gs.xsltParams.site_name; var nodeID = this.id; // documentID, hopefully contains section ID too var metaname = "GPS.mapOverlay"; // collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, responseFunction gs.functions.setArchivesMetadata(collection, site_name, nodeID, metaname, 0, JSON.stringify(json_overlays), null, "override", function(){ console.log("SAVED"); } ); gs.functions.getArchivesMetadata(collection, site_name, nodeID, metaname, 0, function(responseText){ var JSONString = responseText.getValue(); that.savedOverlays = JSONString; } ); } // TODO: When finished testing, can remove this debug function that just prints to console MapEditor.prototype.printHistory = function () { console.log("prev", this.mapEditorHistory.prevOverlays); console.log("present ", this.mapEditorHistory.presentOverlays); console.log("undone ", this.mapEditorHistory.undoneOverlays); console.log("@@@@ selectedShapes: " + this.selectedShapes); } // to be called after reading back in stored JSON from archives meta MapEditor.prototype.LOAD = function (json_str, nodeID) { this.mapEditorHistory.historyOverlayPush(); // This seems to convert the map_store object into an array and forces array index access, instead of convenient property access using nodeID //Object.values(gsmap_store)[0]; // Always gets top level section's map-editor, not what we want. // Get the map editor for the nodeID, as we're asked to load that editor var map_editor = gsmap_store["map-"+nodeID]; var new_overlays = ShapesUtil.JSONToOverlays(json_str); for (var i=0; i