Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
27d12c20bc | ||
|
6a62d14102 | ||
|
2abe4f567b | ||
|
dbd13415df | ||
|
bb4da68ef5 | ||
|
3735dbd242 | ||
|
c701170a07 | ||
|
8e29829e96 | ||
|
0fb4f8fed6 | ||
|
eaa8cb5fc0 | ||
|
92ecab7c44 | ||
|
9371e1efb7 |
2
.github/workflows/build.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3.0.0
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
|
||||
- name: Set up nodejs
|
||||
uses: actions/setup-node@v3
|
||||
|
6
.github/workflows/go-test.yml
vendored
@ -11,10 +11,12 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3.0.0
|
||||
with:
|
||||
go-version: '1.16'
|
||||
go-version: '1.17'
|
||||
|
||||
- name: test
|
||||
run: go test ./... -coverprofile=profile.cov
|
||||
run: |
|
||||
cd public && npm ci && cd ..
|
||||
go test ./... -coverprofile=profile.cov
|
||||
|
||||
- uses: shogo82148/actions-goveralls@v1.6.0
|
||||
with:
|
||||
|
@ -2,6 +2,7 @@ package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"mapserver/coords"
|
||||
"mapserver/db"
|
||||
"mapserver/public"
|
||||
@ -24,7 +25,7 @@ type Sqlite3Accessor struct {
|
||||
func (db *Sqlite3Accessor) Migrate() error {
|
||||
|
||||
//RW connection
|
||||
rwdb, err := sql.Open("sqlite", db.filename+"?mode=rw")
|
||||
rwdb, err := sql.Open("sqlite", "file:"+db.filename+"?mode=rw")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -164,18 +165,21 @@ func (db *Sqlite3Accessor) GetBlock(pos *coords.MapBlockCoords) (*db.Block, erro
|
||||
}
|
||||
|
||||
func New(filename string) (*Sqlite3Accessor, error) {
|
||||
db, err := sql.Open("sqlite", filename+"?mode=ro")
|
||||
db, err := sql.Open("sqlite", "file:"+filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// limit connection and set a busy-timeout to prevent errors if the db should be locked sometimes
|
||||
db.SetMaxOpenConns(1)
|
||||
_, err = db.Exec("pragma busy_timeout = 5000;")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec("pragma journal_mode = wal;")
|
||||
if err != nil {
|
||||
return nil, errors.New("could not set db to wal-mode, please stop the minetest-server to allow this one-time fixup")
|
||||
}
|
||||
|
||||
sq := &Sqlite3Accessor{db: db, filename: filename}
|
||||
return sq, nil
|
||||
}
|
||||
|
@ -1,32 +1,13 @@
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.leaflet-custom-display {
|
||||
background: #fff;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.mapserver-label-icon {
|
||||
margin-left: -100px !important;
|
||||
margin-top: -100px !important;
|
||||
}
|
||||
height: 100%;
|
||||
}
|
@ -2,12 +2,16 @@ package public
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed js/* css/* pics/* index.html
|
||||
//go:embed node_modules/vue/dist/vue.global.prod.js
|
||||
//go:embed node_modules/vue-router/dist/vue-router.global.prod.js
|
||||
//go:embed node_modules/@fortawesome/fontawesome-free/css/all.min.css
|
||||
//go:embed node_modules/@fortawesome/fontawesome-free/webfonts/*
|
||||
//go:embed node_modules/leaflet/dist/leaflet.js
|
||||
//go:embed node_modules/leaflet/dist/leaflet.css
|
||||
//go:embed node_modules/leaflet/dist/images
|
||||
//go:embed node_modules/leaflet.awesome-markers/dist/leaflet.awesome-markers.css
|
||||
//go:embed colors/*
|
||||
//go:embed css/*
|
||||
//go:embed pics/*
|
||||
//go:embed sql/*
|
||||
//go:embed webfonts/*
|
||||
//go:embed *.html
|
||||
//go:embed *.txt
|
||||
//go:embed js/*
|
||||
var Files embed.FS
|
||||
|
@ -6,10 +6,10 @@
|
||||
<meta name="theme-color" content="#000">
|
||||
|
||||
<!-- styles -->
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/fontawesome.min.css"/>
|
||||
<link rel="stylesheet" href="css/leaflet.css"/>
|
||||
<link rel="stylesheet" href="css/leaflet.awesome-markers.css"/>
|
||||
<link rel="stylesheet" href="node_modules/bootswatch/dist/cyborg/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="node_modules/@fortawesome/fontawesome-free/css/all.min.css"/>
|
||||
<link rel="stylesheet" href="node_modules/leaflet/dist/leaflet.css"/>
|
||||
<link rel="stylesheet" href="node_modules/leaflet.awesome-markers/dist/leaflet.awesome-markers.css"/>
|
||||
<link rel="stylesheet" href="css/custom.css"/>
|
||||
|
||||
<title>Minetest Mapserver</title>
|
||||
@ -21,10 +21,9 @@
|
||||
</div>
|
||||
|
||||
<!-- libraries -->
|
||||
<script src="js/lib/mithril.min.js"></script>
|
||||
<script src="js/lib/leaflet.js"></script>
|
||||
<script src="js/lib/leaflet.awesome-markers.js"></script>
|
||||
<script src="js/lib/moment.min.js"></script>
|
||||
<script src="node_modules/vue/dist/vue.global.prod.js"></script>
|
||||
<script src="node_modules/vue-router/dist/vue-router.global.prod.js"></script>
|
||||
<script src="node_modules/leaflet/dist/leaflet.js"></script>
|
||||
|
||||
<!-- main script -->
|
||||
<script src="js/bundle.js" onerror="import('./js/main.js')"></script>
|
||||
|
@ -1,3 +1 @@
|
||||
bundle.js
|
||||
bundle-stats.js
|
||||
lib/*
|
||||
|
@ -4,9 +4,9 @@
|
||||
"esversion": 6,
|
||||
"browser": true,
|
||||
"globals": {
|
||||
"L": true,
|
||||
"m": true,
|
||||
"THREE": true,
|
||||
"console": true
|
||||
"Vue": true,
|
||||
"VueRouter": true,
|
||||
"console": true,
|
||||
"L": true
|
||||
}
|
||||
}
|
||||
|
2
public/js/api/config.js
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export const get = () => fetch("api/config").then(r => r.json());
|
2
public/js/api/stats.js
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export const get = () => fetch("api/stats").then(r => r.json());
|
5
public/js/app.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
template: /*html*/`
|
||||
<router-view></router-view>
|
||||
`
|
||||
};
|
@ -1,61 +1,73 @@
|
||||
import layerManager from '../LayerManager.js';
|
||||
import { createMap } from '../map/MapFactory.js';
|
||||
|
||||
function setupMap(vnode, id){
|
||||
const map = createMap(
|
||||
id,
|
||||
layerManager.getCurrentLayer().id,
|
||||
+vnode.attrs.zoom,
|
||||
+vnode.attrs.lat,
|
||||
+vnode.attrs.lon
|
||||
);
|
||||
|
||||
vnode.state.map = map;
|
||||
|
||||
function updateHash(){
|
||||
const center = map.getCenter();
|
||||
const layerId = layerManager.getCurrentLayer().id;
|
||||
|
||||
m.route.set(`/map/${layerId}/${map.getZoom()}/` +
|
||||
`${Math.floor(center.lng)}/${Math.floor(center.lat)}`);
|
||||
}
|
||||
|
||||
map.on('zoomend', updateHash);
|
||||
map.on('moveend', updateHash);
|
||||
|
||||
return map;
|
||||
}
|
||||
import SimpleCRS from "../utils/SimpleCRS.js";
|
||||
import RealtimeTileLayer from '../utils/RealtimeTileLayer.js';
|
||||
import ws from '../service/ws.js';
|
||||
import { getLayerById } from "../service/layer.js";
|
||||
import CoordinatesDisplay from "../../old/js/map/CoordinatesDisplay.js";
|
||||
import WorldInfoDisplay from "../utils/WorldInfoDisplay.js";
|
||||
|
||||
export default {
|
||||
props: ["lat", "lon", "zoom", "layerId"],
|
||||
mounted: function() {
|
||||
const layer = getLayerById(this.layerId);
|
||||
console.log("Map::mounted", this.lat, this.lon, this.zoom, this.layerId, layer);
|
||||
|
||||
oninit(){
|
||||
this.id = "map_" + Math.floor(Math.random() * 10000);
|
||||
},
|
||||
const map = L.map(this.$refs.target, {
|
||||
minZoom: 2,
|
||||
maxZoom: 12,
|
||||
center: [this.lat, this.lon],
|
||||
zoom: this.zoom,
|
||||
crs: SimpleCRS,
|
||||
maxBounds: L.latLngBounds(
|
||||
L.latLng(-31000, -31000),
|
||||
L.latLng(31000, 31000)
|
||||
)
|
||||
});
|
||||
|
||||
view(){
|
||||
return m("div", { class: "full-screen", id: this.id });
|
||||
},
|
||||
|
||||
oncreate(vnode){
|
||||
this.map = setupMap(vnode, this.id);
|
||||
},
|
||||
const updateLink = () => {
|
||||
const center = map.getCenter();
|
||||
const lon = Math.floor(center.lng);
|
||||
const lat = Math.floor(center.lat);
|
||||
console.log("Map::updateLink", map.getZoom(), lon, lat);
|
||||
// change hash route
|
||||
this.$router.push({
|
||||
name: "map",
|
||||
params: {
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
zoom: map.getZoom(),
|
||||
layerId: this.layerId
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onupdate(vnode){
|
||||
if (vnode.attrs.layerId != layerManager.getCurrentLayer().id){
|
||||
//layer changed, recreate map
|
||||
this.map.remove();
|
||||
layerManager.setLayerId(vnode.attrs.layerId);
|
||||
this.map = setupMap(vnode, this.id);
|
||||
// listen for route change
|
||||
map.on('zoomend', updateLink);
|
||||
map.on('moveend', updateLink);
|
||||
|
||||
} else {
|
||||
//position/zoom change
|
||||
//this.map.setView([+vnode.attrs.lat, +vnode.attrs.lon], +vnode.attrs.zoom);
|
||||
// add attribution
|
||||
map.attributionControl.addAttribution('<a href="https://github.com/minetest-mapserver/mapserver">Minetest Mapserver</a>');
|
||||
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// TODO: all layers
|
||||
var tileLayer = new RealtimeTileLayer(ws, this.layerId, map);
|
||||
tileLayer.addTo(map);
|
||||
|
||||
// various map tools
|
||||
new CoordinatesDisplay({ position: 'bottomleft' }).addTo(map);
|
||||
new WorldInfoDisplay(ws, { position: 'bottomright' }).addTo(map);
|
||||
|
||||
onremove(){
|
||||
this.map.remove();
|
||||
}
|
||||
};
|
||||
console.log(map);
|
||||
},
|
||||
methods: {
|
||||
updateMap: function() {
|
||||
const layer = getLayerById(this.layerId);
|
||||
console.log("Map::updateMap", this.lat, this.lon, this.zoom, this.layerId, layer);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"$route": "updateMap"
|
||||
},
|
||||
template: /*html*/`
|
||||
<div ref="target" style="height: 100%"></div>
|
||||
`
|
||||
};
|
@ -1,49 +1,47 @@
|
||||
import ws from '../service/ws.js';
|
||||
|
||||
export default function(info){
|
||||
|
||||
var timeIcon = m("span", { class: "fa fa-sun", style: "color: orange;" });
|
||||
|
||||
if (info.time < 5500 || info.time > 19000) //0 - 24'000
|
||||
timeIcon = m("span", { class: "fa fa-moon", style: "color: blue;" });
|
||||
|
||||
function getHour(){
|
||||
return Math.floor(info.time/1000);
|
||||
}
|
||||
|
||||
function getMinute(){
|
||||
var min = Math.floor((info.time % 1000) / 1000 * 60);
|
||||
return min >= 10 ? min : "0" + min;
|
||||
}
|
||||
|
||||
function getLag(){
|
||||
var color = "green";
|
||||
if (info.max_lag > 0.8)
|
||||
color = "orange";
|
||||
else if (info.max_lag > 1.2)
|
||||
color = "red";
|
||||
|
||||
return [
|
||||
m("span", { class: "fa fa-wifi", style: "color: " + color }),
|
||||
parseInt(info.max_lag*1000),
|
||||
" ms"
|
||||
];
|
||||
}
|
||||
|
||||
function getPlayers(){
|
||||
return [
|
||||
m("span", { class: "fa fa-users" }),
|
||||
info.players ? info.players.length : "0"
|
||||
];
|
||||
}
|
||||
|
||||
return m("div", [
|
||||
getPlayers(),
|
||||
" ",
|
||||
getLag(),
|
||||
" ",
|
||||
m("span", { class: "fa fa-clock" }),
|
||||
timeIcon,
|
||||
getHour(), ":", getMinute()
|
||||
]);
|
||||
|
||||
}
|
||||
export default {
|
||||
data: function(){
|
||||
return {
|
||||
info: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
infoListener: function(info){
|
||||
this.info = info;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lagColor: function(){
|
||||
if (this.info.max_lag > 0.8)
|
||||
return "orange";
|
||||
else if (this.info.max_lag > 1.2)
|
||||
return "red";
|
||||
else
|
||||
return "green";
|
||||
},
|
||||
time: function() {
|
||||
const min = Math.floor((this.info.time % 1000) / 1000 * 60);
|
||||
return Math.floor(this.info.time/1000) + ":" + (min >= 10 ? min : "0" + min);
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
// bind infoListener to this
|
||||
this.infoListener = this.infoListener.bind(this);
|
||||
ws.addListener("minetest-info", this.infoListener);
|
||||
},
|
||||
beforeUnmount: function() {
|
||||
ws.removeListener("minetest-info", this.infoListener);
|
||||
},
|
||||
template: /*html*/`
|
||||
<div v-if="info">
|
||||
<span v-if="info.players">
|
||||
<span class="fa fa-users"></span> {{ info.players.length }}
|
||||
</span>
|
||||
<span class="fa fa-wifi" v-bind:style="{ 'color': lagColor }"></span> {{ parseInt(info.max_lag*1000) }} ms
|
||||
<span class="fa fa-clock"></span> {{ time }}
|
||||
<span v-if="info.time < 5500 || info.time > 19000" class="fa fa-moon" style="color: blue;"></span>
|
||||
<span v-else class="fa fa-sun" style="color: orange;"></span>
|
||||
</div>
|
||||
`
|
||||
};
|
@ -1,21 +1,30 @@
|
||||
|
||||
import { getConfig } from './api.js';
|
||||
import App from './app.js';
|
||||
import routes from './routes.js';
|
||||
import wsChannel from './WebSocketChannel.js';
|
||||
import config from './config.js';
|
||||
import { hashCompat } from './compat.js';
|
||||
import layerManager from './LayerManager.js';
|
||||
import { get as getConfig } from './api/config.js';
|
||||
import configStore from './store/config.js';
|
||||
import ws from './service/ws.js';
|
||||
|
||||
// hash route compat
|
||||
hashCompat();
|
||||
function start() {
|
||||
// create router instance
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHashHistory(),
|
||||
routes: routes
|
||||
});
|
||||
|
||||
getConfig()
|
||||
.then(cfg => {
|
||||
layerManager.setup(cfg.layers);
|
||||
config.set(cfg);
|
||||
wsChannel.connect();
|
||||
m.route(document.getElementById("app"), "/map/0/12/0/0", routes);
|
||||
})
|
||||
.catch(e => {
|
||||
document.getElementById("app").innerHTML = e;
|
||||
});
|
||||
// start vue
|
||||
const app = Vue.createApp(App);
|
||||
app.use(router);
|
||||
app.mount("#app");
|
||||
}
|
||||
|
||||
// fetch config from server first
|
||||
getConfig().then(cfg => {
|
||||
// copy config to store
|
||||
Object.keys(cfg).forEach(k => configStore[k] = cfg[k]);
|
||||
|
||||
// start websocket/polling
|
||||
ws.connect();
|
||||
|
||||
// start app
|
||||
start();
|
||||
});
|
15
public/js/pages/MapPage.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Map from "../components/Map.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
"map-component": Map
|
||||
},
|
||||
template: /*html*/`
|
||||
<map-component
|
||||
:lat="$route.params.lat"
|
||||
:lon="$route.params.lon"
|
||||
:zoom="$route.params.zoom"
|
||||
:layerId="$route.params.layerId"
|
||||
/>
|
||||
`
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
|
||||
export default {
|
||||
export default [{
|
||||
input: 'main.js',
|
||||
output: {
|
||||
file :'bundle.js',
|
||||
@ -7,4 +7,4 @@ export default {
|
||||
sourcemap: true,
|
||||
compact: true
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
@ -1,8 +1,8 @@
|
||||
import MapPage from "./pages/MapPage.js";
|
||||
|
||||
import Map from './components/Map.js';
|
||||
import Search from './components/Search.js';
|
||||
|
||||
export default {
|
||||
"/map/:layerId/:zoom/:lon/:lat": Map,
|
||||
"/search/:query": Search
|
||||
};
|
||||
export default [{
|
||||
path: "/map/:layerId/:zoom/:lon/:lat", name: "map", component: MapPage
|
||||
},{
|
||||
path: "/", redirect: "/map/0/13/0/0"
|
||||
}];
|
||||
|
4
public/js/service/layer.js
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
import store from '../store/config.js';
|
||||
|
||||
export const getLayerById = id => store.layers.find(l => l.id == id);
|
72
public/js/service/ws.js
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
import { get } from '../api/stats.js';
|
||||
|
||||
class WebSocketChannel {
|
||||
constructor(){
|
||||
this.wsUrl = window.location.protocol.replace("http", "ws") +
|
||||
"//" + window.location.host +
|
||||
window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/")) +
|
||||
"/api/ws";
|
||||
|
||||
this.listenerMap = {/* type -> [listeners] */};
|
||||
}
|
||||
|
||||
addListener(type, listener){
|
||||
var list = this.listenerMap[type];
|
||||
if (!list){
|
||||
list = [];
|
||||
this.listenerMap[type] = list;
|
||||
}
|
||||
|
||||
list.push(listener);
|
||||
}
|
||||
|
||||
removeListener(type, listener){
|
||||
var list = this.listenerMap[type];
|
||||
if (!list){
|
||||
return;
|
||||
}
|
||||
|
||||
this.listenerMap[type] = list.filter(l => l != listener);
|
||||
}
|
||||
|
||||
connect(){
|
||||
var ws = new WebSocket(this.wsUrl);
|
||||
var self = this;
|
||||
|
||||
ws.onmessage = function(e){
|
||||
var event = JSON.parse(e.data);
|
||||
//rendered-tile, mapobject-created, mapobjects-cleared
|
||||
|
||||
var listeners = self.listenerMap[event.type];
|
||||
if (listeners){
|
||||
listeners.forEach(function(listener){
|
||||
listener(event.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function fallbackPolling(){
|
||||
get().then(function(stats){
|
||||
if (!stats){
|
||||
// no stats (yet)
|
||||
return;
|
||||
}
|
||||
|
||||
var listeners = self.listenerMap["minetest-info"];
|
||||
if (listeners){
|
||||
listeners.forEach(function(listener){
|
||||
listener(stats);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ws.onerror = function(){
|
||||
//fallback to polling stats
|
||||
setInterval(fallbackPolling, 2000);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebSocketChannel();
|
2
public/js/store/config.js
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export default Vue.reactive({});
|
34
public/js/utils/CoordinatesDisplay.js
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
export default L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
var div = L.DomUtil.create('div', 'leaflet-bar leaflet-custom-display');
|
||||
var hoverCoord, clickCoord;
|
||||
|
||||
function updateHover(ev){
|
||||
hoverCoord = ev.latlng;
|
||||
update();
|
||||
}
|
||||
|
||||
function updateClick(ev){
|
||||
clickCoord = ev.latlng;
|
||||
update();
|
||||
}
|
||||
|
||||
function update(){
|
||||
var html = "";
|
||||
if (hoverCoord)
|
||||
html = html + "X=" + parseInt(hoverCoord.lng) + " Z=" + parseInt(hoverCoord.lat);
|
||||
|
||||
if (clickCoord)
|
||||
html = html + " (marked: X=" + parseInt(clickCoord.lng) + " Z=" + parseInt(clickCoord.lat) + ")";
|
||||
|
||||
div.innerHTML = html;
|
||||
}
|
||||
|
||||
map.on('mousemove', updateHover);
|
||||
map.on('click', updateClick);
|
||||
map.on('touch', updateClick);
|
||||
|
||||
return div;
|
||||
}
|
||||
});
|
15
public/js/utils/WorldInfoDisplay.js
Normal file
@ -0,0 +1,15 @@
|
||||
import WorldStats from '../components/WorldStats.js';
|
||||
|
||||
export default L.Control.extend({
|
||||
initialize: function(wsChannel, opts) {
|
||||
L.Control.prototype.initialize.call(this, opts);
|
||||
this.wsChannel = wsChannel;
|
||||
},
|
||||
|
||||
onAdd: function() {
|
||||
var div = L.DomUtil.create('div', 'leaflet-bar leaflet-custom-display');
|
||||
const app = Vue.createApp(WorldStats);
|
||||
app.mount(div);
|
||||
return div;
|
||||
}
|
||||
});
|
32
public/old/css/custom.css
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.leaflet-custom-display {
|
||||
background: #fff;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.mapserver-label-icon {
|
||||
margin-left: -100px !important;
|
||||
margin-top: -100px !important;
|
||||
}
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 696 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 618 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 535 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
3
public/old/js/.jshintignore
Normal file
@ -0,0 +1,3 @@
|
||||
bundle.js
|
||||
bundle-stats.js
|
||||
lib/*
|
12
public/old/js/.jshintrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"esversion": 6,
|
||||
"browser": true,
|
||||
"globals": {
|
||||
"L": true,
|
||||
"m": true,
|
||||
"THREE": true,
|
||||
"console": true
|
||||
}
|
||||
}
|
61
public/old/js/components/Map.js
Normal file
@ -0,0 +1,61 @@
|
||||
import layerManager from '../LayerManager.js';
|
||||
import { createMap } from '../map/MapFactory.js';
|
||||
|
||||
function setupMap(vnode, id){
|
||||
const map = createMap(
|
||||
id,
|
||||
layerManager.getCurrentLayer().id,
|
||||
+vnode.attrs.zoom,
|
||||
+vnode.attrs.lat,
|
||||
+vnode.attrs.lon
|
||||
);
|
||||
|
||||
vnode.state.map = map;
|
||||
|
||||
function updateHash(){
|
||||
const center = map.getCenter();
|
||||
const layerId = layerManager.getCurrentLayer().id;
|
||||
|
||||
m.route.set(`/map/${layerId}/${map.getZoom()}/` +
|
||||
`${Math.floor(center.lng)}/${Math.floor(center.lat)}`);
|
||||
}
|
||||
|
||||
map.on('zoomend', updateHash);
|
||||
map.on('moveend', updateHash);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
oninit(){
|
||||
this.id = "map_" + Math.floor(Math.random() * 10000);
|
||||
},
|
||||
|
||||
view(){
|
||||
return m("div", { class: "full-screen", id: this.id });
|
||||
},
|
||||
|
||||
oncreate(vnode){
|
||||
this.map = setupMap(vnode, this.id);
|
||||
},
|
||||
|
||||
onupdate(vnode){
|
||||
if (vnode.attrs.layerId != layerManager.getCurrentLayer().id){
|
||||
//layer changed, recreate map
|
||||
this.map.remove();
|
||||
layerManager.setLayerId(vnode.attrs.layerId);
|
||||
this.map = setupMap(vnode, this.id);
|
||||
|
||||
} else {
|
||||
//position/zoom change
|
||||
//this.map.setView([+vnode.attrs.lat, +vnode.attrs.lon], +vnode.attrs.zoom);
|
||||
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
onremove(){
|
||||
this.map.remove();
|
||||
}
|
||||
};
|
49
public/old/js/components/WorldStats.js
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
export default function(info){
|
||||
|
||||
var timeIcon = m("span", { class: "fa fa-sun", style: "color: orange;" });
|
||||
|
||||
if (info.time < 5500 || info.time > 19000) //0 - 24'000
|
||||
timeIcon = m("span", { class: "fa fa-moon", style: "color: blue;" });
|
||||
|
||||
function getHour(){
|
||||
return Math.floor(info.time/1000);
|
||||
}
|
||||
|
||||
function getMinute(){
|
||||
var min = Math.floor((info.time % 1000) / 1000 * 60);
|
||||
return min >= 10 ? min : "0" + min;
|
||||
}
|
||||
|
||||
function getLag(){
|
||||
var color = "green";
|
||||
if (info.max_lag > 0.8)
|
||||
color = "orange";
|
||||
else if (info.max_lag > 1.2)
|
||||
color = "red";
|
||||
|
||||
return [
|
||||
m("span", { class: "fa fa-wifi", style: "color: " + color }),
|
||||
parseInt(info.max_lag*1000),
|
||||
" ms"
|
||||
];
|
||||
}
|
||||
|
||||
function getPlayers(){
|
||||
return [
|
||||
m("span", { class: "fa fa-users" }),
|
||||
info.players ? info.players.length : "0"
|
||||
];
|
||||
}
|
||||
|
||||
return m("div", [
|
||||
getPlayers(),
|
||||
" ",
|
||||
getLag(),
|
||||
" ",
|
||||
m("span", { class: "fa fa-clock" }),
|
||||
timeIcon,
|
||||
getHour(), ":", getMinute()
|
||||
]);
|
||||
|
||||
}
|
21
public/old/js/main.js
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
import { getConfig } from './api.js';
|
||||
import routes from './routes.js';
|
||||
import wsChannel from './WebSocketChannel.js';
|
||||
import config from './config.js';
|
||||
import { hashCompat } from './compat.js';
|
||||
import layerManager from './LayerManager.js';
|
||||
|
||||
// hash route compat
|
||||
hashCompat();
|
||||
|
||||
getConfig()
|
||||
.then(cfg => {
|
||||
layerManager.setup(cfg.layers);
|
||||
config.set(cfg);
|
||||
wsChannel.connect();
|
||||
m.route(document.getElementById("app"), "/map/0/12/0/0", routes);
|
||||
})
|
||||
.catch(e => {
|
||||
document.getElementById("app").innerHTML = e;
|
||||
});
|
50
public/old/js/map/RealtimeTileLayer.js
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
export default L.TileLayer.extend({
|
||||
|
||||
initialize: function(wsChannel, layerId, map) {
|
||||
L.TileLayer.prototype.initialize.call(this);
|
||||
|
||||
var self = this;
|
||||
this.layerId = layerId;
|
||||
|
||||
wsChannel.addListener("rendered-tile", function(tc){
|
||||
if (tc.layerid != self.layerId){
|
||||
//ignore other layers
|
||||
return;
|
||||
}
|
||||
|
||||
if (tc.zoom != map.getZoom()){
|
||||
//ignore other zoom levels
|
||||
return;
|
||||
}
|
||||
|
||||
var id = self.getImageId(tc.x, tc.y, tc.zoom);
|
||||
var el = document.getElementById(id);
|
||||
|
||||
if (el){
|
||||
//Update src attribute if img found
|
||||
el.src = self.getTileSource(tc.x, tc.y, tc.zoom, true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getTileSource: function(x,y,zoom,cacheBust){
|
||||
return "api/tile/" + this.layerId + "/" + x + "/" + y + "/" + zoom + (cacheBust ? "?_=" + Date.now() : "");
|
||||
},
|
||||
|
||||
getImageId: function(x, y, zoom){
|
||||
return "tile-" + this.layerId + "/" + x + "/" + y + "/" + zoom;
|
||||
},
|
||||
|
||||
createTile: function(coords, done){
|
||||
var tile = document.createElement('img');
|
||||
tile.src = this.getTileSource(coords.x, coords.y, coords.z, true);
|
||||
tile.id = this.getImageId(coords.x, coords.y, coords.z);
|
||||
|
||||
// trigger callbacks
|
||||
tile.onload = () => done(null, tile);
|
||||
tile.onerror = e => done(e, tile);
|
||||
|
||||
return tile;
|
||||
}
|
||||
});
|
6
public/old/js/map/SimpleCRS.js
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
export default L.Util.extend({}, L.CRS.Simple, {
|
||||
scale: function (zoom) {
|
||||
return Math.pow(2, zoom-9);
|
||||
}
|
||||
});
|
10
public/old/js/rollup.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
export default {
|
||||
input: 'main.js',
|
||||
output: {
|
||||
file :'bundle.js',
|
||||
format: 'iife',
|
||||
sourcemap: true,
|
||||
compact: true
|
||||
}
|
||||
};
|
8
public/old/js/routes.js
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
import Map from './components/Map.js';
|
||||
import Search from './components/Search.js';
|
||||
|
||||
export default {
|
||||
"/map/:layerId/:zoom/:lon/:lat": Map,
|
||||
"/search/:query": Search
|
||||
};
|