Skip to content
Snippets Groups Projects
Commit b5d1b0ab authored by Taneli Riihimäki's avatar Taneli Riihimäki
Browse files

Merge user-marker-database-interactions to development

parents 44ad20b0 9279f600
No related branches found
No related tags found
2 merge requests!21Development,!18User marker database interactions
......@@ -178,6 +178,7 @@ div.login button:hover {
/* Editing editable tooltips */
.editable {
display: block;
cursor: text; /* the cursor icon doesn't change by default when hovering on top of the text; overriding */
min-width: 154px;
min-height: 18px;
......
import React, { Component } from 'react';
import '../node_modules/leaflet-draw/dist/leaflet.draw.css'
import './App.css';
import UserMap from './components/UserMap';
import Header from './components/Header';
import React, { Component } from "react";
import "../node_modules/leaflet-draw/dist/leaflet.draw.css";
import "./App.css";
import UserMap from "./components/UserMap";
import Header from "./components/Header";
class App extends Component {
constructor() {
super();
// set initial state
this.state = {
lat: 62.2416479,
lng: 25.7597186,
zoom: 13,
mapUrl: 'https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg'
mapUrl: "https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg",
currentGameId: null
};
this.handleLayerChange = this.handleLayerChange.bind(this);
this.handleGameChange = this.handleGameChange.bind(this);
}
// Toggles through the list and changes the mapUrl state
handleLayerChange = () => {
const maps = ['https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg', 'https://tiles.kartat.kapsi.fi/peruskartta/{z}/{x}/{y}.jpg','https://tiles.kartat.kapsi.fi/ortokuva/{z}/{x}/{y}.jpg'];
this.setState({mapUrl: maps.indexOf(this.state.mapUrl) < maps.length - 1 ? maps[maps.indexOf(this.state.mapUrl) + 1] : maps[0]})
const maps = [
"https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg",
"https://tiles.kartat.kapsi.fi/peruskartta/{z}/{x}/{y}.jpg",
"https://tiles.kartat.kapsi.fi/ortokuva/{z}/{x}/{y}.jpg"
];
this.setState({
mapUrl:
maps.indexOf(this.state.mapUrl) < maps.length - 1
? maps[maps.indexOf(this.state.mapUrl) + 1]
: maps[0]
});
};
// function to be sent to Header -> GameList to get changed game ID
handleGameChange = gameId => {
this.setState({
currentGameId: gameId
});
};
render() {
const initialPosition = [this.state.lat, this.state.lng];
return (
<div>
<UserMap position={initialPosition} zoom={this.state.zoom} mapUrl={this.state.mapUrl} />,
<Header handleLayerChange={this.handleLayerChange} />
<UserMap
position={initialPosition}
zoom={this.state.zoom}
mapUrl={this.state.mapUrl}
currentGameId={this.state.currentGameId}
/>
,
<Header
handleLayerChange={this.handleLayerChange}
handleGameChange={this.handleGameChange}
/>
</div>
);
}
......
......@@ -2,16 +2,27 @@ import React, { Component } from "react";
import { EditControl } from "react-leaflet-draw";
import L from "leaflet";
import "leaflet-draw";
import { FeatureGroup, Marker, Polygon, Polyline } from "react-leaflet";
import {
FeatureGroup,
Circle,
Marker,
Polygon,
Polyline,
Rectangle,
Tooltip
} from "react-leaflet";
// an empty icon for textboxes
let noIcon = L.divIcon({
className: "",
iconSize: [20, 20],
iconAnchor: [10, 20]
});
// class for text field
L.Draw.MarkerTextBox = L.Draw.Marker.extend({
options: {
icon: L.divIcon({
className: "", // empty class to override default styling
iconSize: [20, 20],
iconAnchor: [10, 20]
}),
icon: noIcon,
repeatMode: false,
interactive: true
},
......@@ -80,8 +91,12 @@ class DrawTools extends Component {
if (e.layerType === "textbox") {
// have to create tooltip as a DOM element to allow text selecting. maybe
let tooltip = L.DomUtil.create("div", "editable");
// need ids for tooltips to be able to add a blur event to them
tooltip.innerHTML =
'<div contenteditable="true" placeholder="Click here and type"></div>';
'<div contenteditable="true" placeholder="Click out to save" id="' +
e.layer._leaflet_id +
'"></div>';
e.layer.bindTooltip(tooltip, {
permanent: true,
......@@ -106,54 +121,113 @@ class DrawTools extends Component {
// manually removing it so that the placeholder text can show
if (
tooltip.innerHTML ===
'<div placeholder="Click here and type" contenteditable="true"><br></div>' ||
'<div placeholder="Click out to save" contenteditable="true" id ="' +
e.layer._leaflet_id +
"><br></div>" ||
tooltip.innerHTML ===
'<div placeholder="Click here and type" contenteditable="true"><div><br></div></div>'
'<div placeholder="Click out to save" contenteditable="true" id ="' +
e.layer._leaflet_id +
"><div><br></div></div>"
) {
tooltip.innerHTML =
'<div placeholder="Click here and type" contenteditable="true"></div>';
'<div placeholder="Click out to save" contenteditable="true" id ="' +
e.layer._leaflet_id +
"></div>";
}
});
// blur event listener can't be given straight to a layer
// getting element by ID and adding an event listener to the element
document
.getElementById(e.layer._leaflet_id)
.addEventListener(
"blur",
this.makeGeoJSON.bind(this, e.layerType, e.layer)
); // can't put this.makeGeoJSON(e) straight, as it calls the function
document.getElementById(e.layer._leaflet_id).focus();
console.log(e.layer);
return; // only sending the textbox to database until text has been written
} // end if (e.layerType === "textbox")
// turning layer data to GeoJSON
this.makeGeoJSON(e.layer);
this.makeGeoJSON(e.layerType, e.layer);
}; // end _onCreated
_onEditMove = e => {
console.log("_onEditMove e:");
console.log(e);
// to be added once back-end has functionality to recognize ids
// this.props.sendGeoJSON(e.layer);
};
// turn layer to GeoJSON data
makeGeoJSON = (layerType, layer) => {
// setting the format in which the data will be sent
let geoJSON = {
data: layer.toGeoJSON(),
mapDrawingId: layer.options.id
};
_onEditResize = e => {
console.log("_onEditResize e:");
console.log(e);
};
// setting properties
if (layerType === "textbox") {
if (layer._tooltip._content.innerText) {
geoJSON.data.properties.text = layer._tooltip._content.innerText;
} else {
return;
}
} else if (layerType === "circle") {
geoJSON.data.properties.radius = layer._mRadius; // layer.options.radius doesn't update for some reason; need to use _mRadius instead
} else if (layerType === "rectangle") {
// rectangle is a simple true/false property to recognize a rectangle
// so that Polygons with this property can be inserted into map with rectangle functionalities instead of Polygon's
geoJSON.data.properties.rectangle = true;
}
geoJSON.data.properties.color = layer.options.color;
_onEditVertex = e => {
console.log("_onEditVertex e:");
console.log(e);
// to be added once back-end has functionality to recognize ids
// this.props.sendGeoJSON(e.poly);
// send item to database, and receive an ID for the layer
this.props.sendGeoJSON(geoJSON, false);
};
_onEditDeleteStart() {
_onEditDeleteStart = () => {
this.setState({ editModeActive: true });
}
};
_onEditDeleteStop() {
_onEditDeleteStop = () => {
this.setState({ editModeActive: false });
}
};
_onEdited = e => {
// layers are saved in a rather curious format. they're not in an array, so need to make an array first
let editedLayers = e.layers;
let idsToEdit = [];
editedLayers.eachLayer(function(layer) {
idsToEdit.push(layer);
});
idsToEdit.map(layer => {
// checking the contents of the layer to determine its type, as it's not explicitly stated
if (layer.options.bounds) {
this.makeGeoJSON("rectangle", layer);
} else if (layer.options.radius) {
this.makeGeoJSON("circle", layer);
} else if (layer.options.text) {
this.makeGeoJSON("textbox", layer);
} else {
this.makeGeoJSON(null, layer);
}
});
};
_onDeleted = e => {
console.log(e.layers._layers);
/* to be added once back-end functionality is available
for(layer in e.layers._layers) {
this.sendGeoJSON(layer.options.id);
}
*/
// layers are saved in a rather curious format. they're not in an array, so need to make an array first
let deletedLayers = e.layers;
let idsToDelete = [];
deletedLayers.eachLayer(function(layer) {
idsToDelete.push(layer);
});
idsToDelete.map(layer => {
let geoJSON = {
data: layer.toGeoJSON(),
mapDrawingId: layer.options.id
};
this.props.sendGeoJSON(geoJSON, true);
});
};
shouldComponentUpdate() {
......@@ -161,40 +235,6 @@ class DrawTools extends Component {
return !this.state.editModeActive;
}
// turn layer to GeoJSON data and add it to an array of all GeoJSON data of the current map
makeGeoJSON = e => {
let geoJSON = e.toGeoJSON();
console.log(
"UserMapille lähetettävä layeri: " + JSON.stringify(geoJSON, null, 4)
); // printing GeoJSON data of the previous object create
this.props.sendGeoJSON(geoJSON);
};
addFetchedLayerToMap = (id, feature) => {
if (feature.geometry.type === "Point") {
// GeoJSON saves latitude first, not longitude like usual. swapping
let position = [
feature.geometry.coordinates[1],
feature.geometry.coordinates[0]
];
// keys are required to be able to edit
return <Marker key={Math.random()} position={position} id={id} />;
} else if (feature.geometry.type === "Polygon") {
// polygons have, for some reason, an extra single element array above other arrays. no other objects have this
let coords = feature.geometry.coordinates[0];
let positions = coords.map(item => {
return [item[1], item[0]];
});
return <Polygon key={Math.random()} positions={positions} id={id} />;
} else if (feature.geometry.type === "LineString") {
let coords = feature.geometry.coordinates;
let positions = coords.map(item => {
return [item[1], item[0]];
});
return <Polyline key={Math.random()} positions={positions} id={id} />;
}
};
render() {
return (
// "It's important to wrap EditControl component into FeatureGroup component from react-leaflet.
......@@ -204,11 +244,9 @@ class DrawTools extends Component {
<EditControl
position="topright"
onCreated={this._onCreated}
onEdited={this._onEdited}
onEditStart={this._onEditDeleteStart}
onEditStop={this._onEditDeleteStop}
onEditMove={this._onEditMove}
onEditResize={this._onEditResize}
onEditVertex={this._onEditVertex}
onDeleted={this._onDeleted}
onDeleteStart={this._onEditDeleteStart}
onDeleteStop={this._onEditDeleteStop}
......@@ -250,12 +288,105 @@ class DrawTools extends Component {
/>
{/* iterate through every element fetched from back-end */}
{this.props.geoJSONLayer.features.map((feature, arrayIndex) => {
// first element in geoJSONLayer has an extra one element array for some reason
if (arrayIndex === 0) {
return this.addFetchedLayerToMap(feature[0], feature[1][0]);
} else {
return this.addFetchedLayerToMap(feature[0], feature[1]);
{this.props.geoJSONLayer.features.map(feature => {
let id = feature.mapDrawingId;
let coords = feature.data.geometry.coordinates;
let type = feature.data.geometry.type;
let color = feature.data.properties.color;
let radius = feature.data.properties.radius;
let text = feature.data.properties.text;
let rectangle = feature.data.properties.rectangle;
if (type === "Point") {
// GeoJSON saves latitude first, not longitude like usual. swapping
let position = [coords[1], coords[0]];
if (radius) {
return (
// keys are required to be able to edit
<Circle
key={Math.random()}
center={position}
id={id}
radius={radius}
color={color}
/>
);
} else if (text) {
return (
<Marker
key={Math.random()}
position={position}
id={id}
color={color}
icon={noIcon}
>
<Tooltip
direction="bottom"
permanent
className="editable"
interactive={true}
>
<div class="editable">
<div
contenteditable="true"
placeholder="Click out to save"
>
{text}
</div>
</div>
</Tooltip>
</Marker>
);
} else {
// unknown if color changes anything. need to test
return (
<Marker
key={Math.random()}
position={position}
id={id}
color={color}
/>
);
}
} else if (rectangle) {
// instead of an array of four coordinates, rectangles only have two corners
let bounds = coords[0].map(coord => {
return [coord[1], coord[0]];
});
return (
<Rectangle
key={Math.random()}
bounds={bounds}
id={id}
color={color}
/>
);
} else if (type === "Polygon") {
// Polygon coordinates are wrapped under a one element array, for some reason
let positions = coords[0].map(coord => {
return [coord[1], coord[0]];
});
return (
<Polygon
key={Math.random()}
positions={positions}
id={id}
color={color}
/>
);
} else if (type === "LineString") {
// Polyline coordinates are a normal array, unlike Polygon
let positions = coords.map(coord => {
return [coord[1], coord[0]];
});
return (
<Polyline
key={Math.random()}
positions={positions}
id={id}
color={color}
/>
);
}
})}
</FeatureGroup>
......
......@@ -23,8 +23,10 @@ class GameList extends React.Component {
.then(games => {
this.setState({
games: games,
selectedGame: games !== null && games[0].id
selectedGame: games !== undefined && games[0].id
});
// taking the initialized gameID to UserMap.js (GameList.js -> Header.js -> App.js -> UserMap.js)
this.props.handleGameChange(games[0].id);
})
.catch(error => {
console.log(error);
......@@ -32,9 +34,15 @@ class GameList extends React.Component {
}
handleChange = e => {
this.setState({
selectedGame: e.target.value
});
this.setState(
{
selectedGame: e.target.value
},
() => {
// taking the changed gameID to UserMap.js (GameList.js -> Header.js -> App.js -> UserMap.js)
this.props.handleGameChange(this.state.selectedGame);
}
);
};
handleEditClick = e => {
......
......@@ -6,6 +6,10 @@ import GameList from "./GameList";
import NewGameForm from "./NewGameForm";
class Header extends React.Component {
constructor(props) {
super(props);
}
state = {
form: "", // Popup form (login, register etc.)
username: null,
......@@ -94,7 +98,7 @@ class Header extends React.Component {
<button id="changeLayerButton" onClick={this.props.handleLayerChange}>
change layer
</button>
<GameList />
<GameList handleGameChange={this.props.handleGameChange} />
</div>
{this.state.form === "register" && (
<RegisterForm
......
......@@ -13,7 +13,8 @@ class UserMap extends Component {
geoJSONLayer: {
type: "FeatureCollection",
features: []
}
},
currentGameId: null
};
this.sendGeoJSON = this.sendGeoJSON.bind(this);
......@@ -25,27 +26,67 @@ class UserMap extends Component {
this.getCurrentPosition(position => {
this.setCurrentPosition(position);
});
this.fetchGeoJSON();
}
componentDidUpdate() {
// check if game ID has changed and fetch that game's drawings
if (this.state.currentGameId !== this.props.currentGameId) {
this.setState({
currentGameId: this.props.currentGameId
});
this.fetchGeoJSON();
}
}
// Sends the players drawings to the backend (and database)
sendGeoJSON(layerToDatabase) {
fetch("http://localhost:5000/draw/mapdrawing/insert-location", {
method: "PUT",
headers: {
Authorization: "Bearer " + sessionStorage.getItem("token"),
Accept: "application/json",
"Content-Type": "application/json"
},
// need to add id once back-end is ready for it
body: JSON.stringify({
type: "FeatureCollection",
features: layerToDatabase
})
});
sendGeoJSON(layerToDatabase, isDeleted) {
// isDeleted is used to determine the drawing's drawingIsActive status
// otherwise the fetch functions are the same in both if and else. any smarter way to do this?
if (isDeleted === true) {
fetch(
"http://localhost:5000/draw/mapdrawing/" + this.props.currentGameId,
{
method: "PUT",
headers: {
Authorization: "Bearer " + sessionStorage.getItem("token"),
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
type: "FeatureCollection",
drawingIsActive: false,
mapDrawingId: layerToDatabase.mapDrawingId,
data: layerToDatabase.data
})
}
);
} else {
fetch(
"http://localhost:5000/draw/mapdrawing/" + this.props.currentGameId,
{
method: "PUT",
headers: {
Authorization: "Bearer " + sessionStorage.getItem("token"),
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
type: "FeatureCollection",
drawingIsActive: true,
mapDrawingId: layerToDatabase.mapDrawingId,
data: layerToDatabase.data
})
}
);
}
// get the layers again to stop updating with old objects
this.fetchGeoJSON();
}
// Get the drawings from the backend and add them to the state, so they can be drawn
fetchGeoJSON() {
fetch("http://localhost:5000/draw/mapdrawing/insert-location", {
fetch("http://localhost:5000/draw/map/" + this.props.currentGameId, {
method: "GET",
headers: {
Authorization: "Bearer " + sessionStorage.getItem("token"),
......@@ -55,10 +96,9 @@ class UserMap extends Component {
})
.then(res => res.json())
.then(data => {
console.log(data);
let newFeatures = [];
data.map(item => {
newFeatures.push([item.id, item.features]);
newFeatures.push(item);
});
this.setState({
......@@ -67,7 +107,11 @@ class UserMap extends Component {
features: [...newFeatures]
}
});
})
.catch(error => {
console.log(error);
});
console.log(this.state.geoJSONLayer);
}
componentWillUnmount() {
......@@ -136,6 +180,7 @@ class UserMap extends Component {
position={this.props.position}
sendGeoJSON={this.sendGeoJSON}
geoJSONLayer={this.state.geoJSONLayer}
currentGameId={this.props.currentGameId}
/>
{this.state.ownLat !== null && (
<Marker position={[this.state.ownLat, this.state.ownLng]}>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment