diff options
author | Anton Tananaev <anton@traccar.org> | 2024-04-06 08:41:24 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2024-04-06 08:41:24 -0700 |
commit | 4d0c0e4b2d1600d65a3936be1b88e692188e2c34 (patch) | |
tree | 351480d0c117e889532d3758ce3c98fedce3f8ab | |
parent | 852155da8ebf5672355d7d17c94eeab29baed275 (diff) | |
download | trackermap-web-4d0c0e4b2d1600d65a3936be1b88e692188e2c34.tar.gz trackermap-web-4d0c0e4b2d1600d65a3936be1b88e692188e2c34.tar.bz2 trackermap-web-4d0c0e4b2d1600d65a3936be1b88e692188e2c34.zip |
New React based simple app
-rw-r--r-- | web/simple/app.js | 140 | ||||
-rw-r--r-- | web/simple/index.html | 223 |
2 files changed, 217 insertions, 146 deletions
diff --git a/web/simple/app.js b/web/simple/app.js deleted file mode 100644 index 0c44bae5..00000000 --- a/web/simple/app.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, "find", { - value: function(predicate) { - var value; - for (var i = 0; i < this.length; i++) { - value = this[i]; - if (predicate.call(arguments[1], value, i, this)) { - return value; - } - } - return undefined; - } - }); -} - -var getQueryParameter = function(name) { - return (window.location.search.match('[?&]' + name + '=([^&]*)') || [])[1]; -}; - -var url = window.location.protocol + '//' + window.location.host; -var token = getQueryParameter('token'); - -var style = function (label) { - return new ol.style.Style({ - image: new ol.style.Circle({ - fill: new ol.style.Fill({ - color: 'teal' - }), - stroke: new ol.style.Stroke({ - color: 'black', - width: 2 - }), - radius: 7 - }), - text: new ol.style.Text({ - text: label, - fill: new ol.style.Fill({ - color: 'black' - }), - stroke: new ol.style.Stroke({ - color: 'white', - width: 2 - }), - font: 'bold 12px sans-serif', - offsetY: -16 - }) - }); -}; - -var source = new ol.source.Vector(); - -var markers = {}; - -var map = new ol.Map({ - layers: [ - new ol.layer.Tile({ - source: new ol.source.OSM() - }), - new ol.layer.Vector({ - source: source - }) - ], - target: 'map', - view: new ol.View({ - center: ol.proj.fromLonLat([0, 0]), - zoom: 2 - }) -}); - -var ajax = function (method, url, callback) { - var xhr = new XMLHttpRequest(); - xhr.withCredentials = true; - xhr.open(method, url, true); - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - callback(JSON.parse(xhr.responseText)); - } - }; - if (method == 'POST') { - xhr.setRequestHeader('Content-type', 'application/json'); - } - xhr.send() -}; - -ajax('GET', url + '/api/server', function(server) { - ajax('GET', url + '/api/session?token=' + token, function(user) { - - map.getView().setCenter(ol.proj.fromLonLat([ - parseFloat(getQueryParameter('longitude')) || user.longitude || server.longitude || 0.0, - parseFloat(getQueryParameter('latitude')) || user.latitude || server.latitude || 0.0 - ])); - map.getView().setZoom(parseFloat(getQueryParameter('zoom')) || user.zoom || server.zoom || 2); - - ajax('GET', url + '/api/devices', function(devices) { - - var socket = new WebSocket('ws' + url.substring(4) + '/api/socket'); - - socket.onclose = function (event) { - console.log('socket closed'); - }; - - socket.onmessage = function (event) { - var data = JSON.parse(event.data); - if (data.positions) { - for (i = 0; i < data.positions.length; i++) { - var position = data.positions[i]; - var marker = markers[position.deviceId]; - var point = new ol.geom.Point(ol.proj.fromLonLat([position.longitude, position.latitude])); - if (!marker) { - var device = devices.find(function (device) { return device.id === position.deviceId }); - marker = new ol.Feature(point); - marker.setStyle(style(device.name)); - markers[position.deviceId] = marker; - source.addFeature(marker); - } else { - marker.setGeometry(point); - } - } - } - }; - - }); - }); -}); diff --git a/web/simple/index.html b/web/simple/index.html index 01300677..63eec3f7 100644 --- a/web/simple/index.html +++ b/web/simple/index.html @@ -1,14 +1,225 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> <meta charset="UTF-8"> -<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> +<meta name="viewport" content="width=device-width, initial-scale=1"> <title>Traccar</title> -<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.1/ol.css" type="text/css"> </head> <body style="margin: 0; padding: 0;"> -<div id="map" style="width: 100%; height: 100%; position:fixed;"></div> -<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.20.1/ol.js" type="text/javascript"></script> -<script id="loadScript" src="app.js"></script> +<div id="content" style="width: 100%; height: 100%; position:fixed;"> +<script src="https://unpkg.com/react@17/umd/react.development.js"></script> +<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> +<script src="https://unpkg.com/maplibre-gl@4.1.2/dist/maplibre-gl.js"></script> +<link href="https://unpkg.com/maplibre-gl@4.1.2/dist/maplibre-gl.css" rel="stylesheet"> +<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> +<script type="text/babel"> + + const LoginScreen = ({ server, setServer, setUser }) => { + const [email, setEmail] = React.useState(''); + const [password, setPassword] = React.useState(''); + + const handleSubmit = (event) => { + event.preventDefault(); + const fetchData = async () => { + if (server.newServer) { + const response = await fetch('/api/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: email, email, password }), + }); + if (response.ok) { + setServer({ ...server, newServer: false }); + } + } else { + const query = `email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`; + const response = await fetch('/api/session', { + method: 'POST', + body: new URLSearchParams(query), + }); + if (response.ok) { + setUser(await response.json()); + } + } + } + fetchData(); + }; + + const formStyle = { + width: '180px', + margin: '16px', + display: 'flex', + flexDirection: 'column', + gap: '8px', + }; + + return ( + <form onSubmit={handleSubmit} style={formStyle}> + <input + value={email} + onChange={(e) => setEmail(e.target.value)} + placeholder="Email" + /> + <input + password={password} + onChange={(e) => setPassword(e.target.value)} + placeholder="Password" + type="password" + /> + <button type="submit"> + {server.newServer ? 'Register' : 'Login'} + </button> + </form> + ); + }; + + const MainScreen = () => { + const mapContainer = React.useRef(); + const map = React.useRef(); + + React.useEffect(() => { + map.current = new maplibregl.Map({ + container: mapContainer.current, + style: 'https://demotiles.maplibre.org/style.json', + center: [0, 0], + zoom: 1, + }); + }, []); + + const [devices, setDevices] = React.useState([]); + + React.useEffect(() => { + const fetchData = async () => { + const devicesResponse = await fetch('/api/devices'); + if (devicesResponse.ok) { + setDevices(await devicesResponse.json()); + } + } + fetchData(); + }, []); + + const [initialized, setInitialized] = React.useState(false); + const [positions, setPositions] = React.useState({}); + + React.useEffect(() => { + if (initialized) { + const url = window.location.protocol + '//' + window.location.host; + const socket = new WebSocket('ws' + url.substring(4) + '/api/socket'); + socket.onmessage = (event) => { + const data = JSON.parse(event.data); + const updatedPositions = {}; + data.positions?.forEach((p) => updatedPositions[p.deviceId] = p); + setPositions({ ...positions, ...updatedPositions }) + }; + } + }, [initialized]); + + const handleAddDevice = (event) => { + event.preventDefault(); + const fetchData = async () => { + const id = prompt('Enter device id'); + const response = await fetch('/api/devices', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: id, + uniqueId: id, + }), + }); + if (response.ok) { + setDevices([...devices, await response.json()]); + } + } + fetchData(); + }; + + React.useEffect(() => { + map.current.on('load', () => { + map.current.addSource('points', { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [], + }, + }); + map.current.addLayer({ + id: 'points', + type: 'circle', + source: 'points', + }); + setInitialized(true); + }); + }, []); + + React.useEffect(() => { + map.current.getSource('points')?.setData({ + type: 'FeatureCollection', + features: Object.values(positions).map((position) => ({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [position.longitude, position.latitude], + }, + })), + }); + }, [positions]); + + const containerStyle = { + width: '100%', + height: '100%', + display: 'flex', + }; + const deviceStyle = { + width: '240px', + }; + const mapStyle = { + height: '100%', + flexGrow: 1, + }; + + return ( + <div style={containerStyle}> + <div style={deviceStyle}> + <ul> + {devices?.map((device) => (<li key={device.id}>{device.name}</li>))} + <li><a href="#" onClick={handleAddDevice}>Add device</a></li> + </ul> + </div> + <div style={mapStyle} ref={mapContainer}></div> + </div> + ); + }; + + const App = () => { + const [server, setServer] = React.useState(); + const [user, setUser] = React.useState(); + + React.useEffect(() => { + const fetchData = async () => { + const serverResponse = await fetch('/api/server'); + if (serverResponse.ok) { + setServer(await serverResponse.json()); + } + const sessionResponse = await fetch('/api/session'); + if (sessionResponse.ok) { + setUser(await sessionResponse.json()); + } + } + fetchData(); + }, []); + + return user ? ( + <MainScreen /> + ) : server ? ( + <LoginScreen + server={server} + setServer={setServer} + setUser={setUser} + /> + ) : ''; + }; + + ReactDOM.render(<App />, document.getElementById('content')); + +</script> </body> </html> |