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 { ...@@ -178,6 +178,7 @@ div.login button:hover {
/* Editing editable tooltips */ /* Editing editable tooltips */
.editable { .editable {
display: block;
cursor: text; /* the cursor icon doesn't change by default when hovering on top of the text; overriding */ cursor: text; /* the cursor icon doesn't change by default when hovering on top of the text; overriding */
min-width: 154px; min-width: 154px;
min-height: 18px; min-height: 18px;
......
import React, { Component } from 'react'; import React, { Component } from "react";
import '../node_modules/leaflet-draw/dist/leaflet.draw.css' import "../node_modules/leaflet-draw/dist/leaflet.draw.css";
import './App.css'; import "./App.css";
import UserMap from './components/UserMap'; import UserMap from "./components/UserMap";
import Header from './components/Header'; import Header from "./components/Header";
class App extends Component { class App extends Component {
constructor() { constructor() {
super(); super();
// set initial state // set initial state
this.state = { this.state = {
lat: 62.2416479, lat: 62.2416479,
lng: 25.7597186, lng: 25.7597186,
zoom: 13, 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.handleLayerChange = this.handleLayerChange.bind(this);
this.handleGameChange = this.handleGameChange.bind(this);
} }
// Toggles through the list and changes the mapUrl state // Toggles through the list and changes the mapUrl state
handleLayerChange = () => { 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']; const maps = [
this.setState({mapUrl: maps.indexOf(this.state.mapUrl) < maps.length - 1 ? maps[maps.indexOf(this.state.mapUrl) + 1] : maps[0]}) "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() { render() {
const initialPosition = [this.state.lat, this.state.lng]; const initialPosition = [this.state.lat, this.state.lng];
return ( return (
<div> <div>
<UserMap position={initialPosition} zoom={this.state.zoom} mapUrl={this.state.mapUrl} />, <UserMap
<Header handleLayerChange={this.handleLayerChange} /> position={initialPosition}
zoom={this.state.zoom}
mapUrl={this.state.mapUrl}
currentGameId={this.state.currentGameId}
/>
,
<Header
handleLayerChange={this.handleLayerChange}
handleGameChange={this.handleGameChange}
/>
</div> </div>
); );
} }
......
...@@ -2,16 +2,27 @@ import React, { Component } from "react"; ...@@ -2,16 +2,27 @@ import React, { Component } from "react";
import { EditControl } from "react-leaflet-draw"; import { EditControl } from "react-leaflet-draw";
import L from "leaflet"; import L from "leaflet";
import "leaflet-draw"; 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 // class for text field
L.Draw.MarkerTextBox = L.Draw.Marker.extend({ L.Draw.MarkerTextBox = L.Draw.Marker.extend({
options: { options: {
icon: L.divIcon({ icon: noIcon,
className: "", // empty class to override default styling
iconSize: [20, 20],
iconAnchor: [10, 20]
}),
repeatMode: false, repeatMode: false,
interactive: true interactive: true
}, },
...@@ -80,8 +91,12 @@ class DrawTools extends Component { ...@@ -80,8 +91,12 @@ class DrawTools extends Component {
if (e.layerType === "textbox") { if (e.layerType === "textbox") {
// have to create tooltip as a DOM element to allow text selecting. maybe // have to create tooltip as a DOM element to allow text selecting. maybe
let tooltip = L.DomUtil.create("div", "editable"); let tooltip = L.DomUtil.create("div", "editable");
// need ids for tooltips to be able to add a blur event to them
tooltip.innerHTML = 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, { e.layer.bindTooltip(tooltip, {
permanent: true, permanent: true,
...@@ -106,54 +121,113 @@ class DrawTools extends Component { ...@@ -106,54 +121,113 @@ class DrawTools extends Component {
// manually removing it so that the placeholder text can show // manually removing it so that the placeholder text can show
if ( if (
tooltip.innerHTML === 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 === 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 = 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") } // end if (e.layerType === "textbox")
// turning layer data to GeoJSON this.makeGeoJSON(e.layerType, e.layer);
this.makeGeoJSON(e.layer);
}; // end _onCreated }; // end _onCreated
_onEditMove = e => { // turn layer to GeoJSON data
console.log("_onEditMove e:"); makeGeoJSON = (layerType, layer) => {
console.log(e); // setting the format in which the data will be sent
// to be added once back-end has functionality to recognize ids let geoJSON = {
// this.props.sendGeoJSON(e.layer); data: layer.toGeoJSON(),
}; mapDrawingId: layer.options.id
};
_onEditResize = e => { // setting properties
console.log("_onEditResize e:"); if (layerType === "textbox") {
console.log(e); 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 => { // send item to database, and receive an ID for the layer
console.log("_onEditVertex e:"); this.props.sendGeoJSON(geoJSON, false);
console.log(e);
// to be added once back-end has functionality to recognize ids
// this.props.sendGeoJSON(e.poly);
}; };
_onEditDeleteStart() { _onEditDeleteStart = () => {
this.setState({ editModeActive: true }); this.setState({ editModeActive: true });
} };
_onEditDeleteStop() { _onEditDeleteStop = () => {
this.setState({ editModeActive: false }); 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 => { _onDeleted = e => {
console.log(e.layers._layers); // layers are saved in a rather curious format. they're not in an array, so need to make an array first
/* to be added once back-end functionality is available let deletedLayers = e.layers;
for(layer in e.layers._layers) { let idsToDelete = [];
this.sendGeoJSON(layer.options.id); 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() { shouldComponentUpdate() {
...@@ -161,40 +235,6 @@ class DrawTools extends Component { ...@@ -161,40 +235,6 @@ class DrawTools extends Component {
return !this.state.editModeActive; 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() { render() {
return ( return (
// "It's important to wrap EditControl component into FeatureGroup component from react-leaflet. // "It's important to wrap EditControl component into FeatureGroup component from react-leaflet.
...@@ -204,11 +244,9 @@ class DrawTools extends Component { ...@@ -204,11 +244,9 @@ class DrawTools extends Component {
<EditControl <EditControl
position="topright" position="topright"
onCreated={this._onCreated} onCreated={this._onCreated}
onEdited={this._onEdited}
onEditStart={this._onEditDeleteStart} onEditStart={this._onEditDeleteStart}
onEditStop={this._onEditDeleteStop} onEditStop={this._onEditDeleteStop}
onEditMove={this._onEditMove}
onEditResize={this._onEditResize}
onEditVertex={this._onEditVertex}
onDeleted={this._onDeleted} onDeleted={this._onDeleted}
onDeleteStart={this._onEditDeleteStart} onDeleteStart={this._onEditDeleteStart}
onDeleteStop={this._onEditDeleteStop} onDeleteStop={this._onEditDeleteStop}
...@@ -250,12 +288,105 @@ class DrawTools extends Component { ...@@ -250,12 +288,105 @@ class DrawTools extends Component {
/> />
{/* iterate through every element fetched from back-end */} {/* iterate through every element fetched from back-end */}
{this.props.geoJSONLayer.features.map((feature, arrayIndex) => { {this.props.geoJSONLayer.features.map(feature => {
// first element in geoJSONLayer has an extra one element array for some reason let id = feature.mapDrawingId;
if (arrayIndex === 0) { let coords = feature.data.geometry.coordinates;
return this.addFetchedLayerToMap(feature[0], feature[1][0]); let type = feature.data.geometry.type;
} else { let color = feature.data.properties.color;
return this.addFetchedLayerToMap(feature[0], feature[1]); 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> </FeatureGroup>
......
...@@ -23,8 +23,10 @@ class GameList extends React.Component { ...@@ -23,8 +23,10 @@ class GameList extends React.Component {
.then(games => { .then(games => {
this.setState({ this.setState({
games: games, 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 => { .catch(error => {
console.log(error); console.log(error);
...@@ -32,9 +34,15 @@ class GameList extends React.Component { ...@@ -32,9 +34,15 @@ class GameList extends React.Component {
} }
handleChange = e => { handleChange = e => {
this.setState({ this.setState(
selectedGame: e.target.value {
}); 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 => { handleEditClick = e => {
......
...@@ -6,6 +6,10 @@ import GameList from "./GameList"; ...@@ -6,6 +6,10 @@ import GameList from "./GameList";
import NewGameForm from "./NewGameForm"; import NewGameForm from "./NewGameForm";
class Header extends React.Component { class Header extends React.Component {
constructor(props) {
super(props);
}
state = { state = {
form: "", // Popup form (login, register etc.) form: "", // Popup form (login, register etc.)
username: null, username: null,
...@@ -94,7 +98,7 @@ class Header extends React.Component { ...@@ -94,7 +98,7 @@ class Header extends React.Component {
<button id="changeLayerButton" onClick={this.props.handleLayerChange}> <button id="changeLayerButton" onClick={this.props.handleLayerChange}>
change layer change layer
</button> </button>
<GameList /> <GameList handleGameChange={this.props.handleGameChange} />
</div> </div>
{this.state.form === "register" && ( {this.state.form === "register" && (
<RegisterForm <RegisterForm
......
...@@ -13,7 +13,8 @@ class UserMap extends Component { ...@@ -13,7 +13,8 @@ class UserMap extends Component {
geoJSONLayer: { geoJSONLayer: {
type: "FeatureCollection", type: "FeatureCollection",
features: [] features: []
} },
currentGameId: null
}; };
this.sendGeoJSON = this.sendGeoJSON.bind(this); this.sendGeoJSON = this.sendGeoJSON.bind(this);
...@@ -25,27 +26,67 @@ class UserMap extends Component { ...@@ -25,27 +26,67 @@ class UserMap extends Component {
this.getCurrentPosition(position => { this.getCurrentPosition(position => {
this.setCurrentPosition(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) // Sends the players drawings to the backend (and database)
sendGeoJSON(layerToDatabase) { sendGeoJSON(layerToDatabase, isDeleted) {
fetch("http://localhost:5000/draw/mapdrawing/insert-location", { // isDeleted is used to determine the drawing's drawingIsActive status
method: "PUT", // otherwise the fetch functions are the same in both if and else. any smarter way to do this?
headers: { if (isDeleted === true) {
Authorization: "Bearer " + sessionStorage.getItem("token"), fetch(
Accept: "application/json", "http://localhost:5000/draw/mapdrawing/" + this.props.currentGameId,
"Content-Type": "application/json" {
}, method: "PUT",
// need to add id once back-end is ready for it headers: {
body: JSON.stringify({ Authorization: "Bearer " + sessionStorage.getItem("token"),
type: "FeatureCollection", Accept: "application/json",
features: layerToDatabase "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 // Get the drawings from the backend and add them to the state, so they can be drawn
fetchGeoJSON() { fetchGeoJSON() {
fetch("http://localhost:5000/draw/mapdrawing/insert-location", { fetch("http://localhost:5000/draw/map/" + this.props.currentGameId, {
method: "GET", method: "GET",
headers: { headers: {
Authorization: "Bearer " + sessionStorage.getItem("token"), Authorization: "Bearer " + sessionStorage.getItem("token"),
...@@ -55,10 +96,9 @@ class UserMap extends Component { ...@@ -55,10 +96,9 @@ class UserMap extends Component {
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
console.log(data);
let newFeatures = []; let newFeatures = [];
data.map(item => { data.map(item => {
newFeatures.push([item.id, item.features]); newFeatures.push(item);
}); });
this.setState({ this.setState({
...@@ -67,7 +107,11 @@ class UserMap extends Component { ...@@ -67,7 +107,11 @@ class UserMap extends Component {
features: [...newFeatures] features: [...newFeatures]
} }
}); });
})
.catch(error => {
console.log(error);
}); });
console.log(this.state.geoJSONLayer);
} }
componentWillUnmount() { componentWillUnmount() {
...@@ -136,6 +180,7 @@ class UserMap extends Component { ...@@ -136,6 +180,7 @@ class UserMap extends Component {
position={this.props.position} position={this.props.position}
sendGeoJSON={this.sendGeoJSON} sendGeoJSON={this.sendGeoJSON}
geoJSONLayer={this.state.geoJSONLayer} geoJSONLayer={this.state.geoJSONLayer}
currentGameId={this.props.currentGameId}
/> />
{this.state.ownLat !== null && ( {this.state.ownLat !== null && (
<Marker position={[this.state.ownLat, this.state.ownLng]}> <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