burguillos.info/js-src/conquer/index.ts

519 lines
20 KiB
TypeScript
Raw Normal View History

import Map from "ol/Map"
2023-11-21 12:53:58 +01:00
import MapEvent from "ol/MapEvent"
import MapBrowserEvent from "ol/MapBrowserEvent"
import View from "ol/View"
import Projection from "ol/proj/Projection.js"
2023-11-13 17:32:12 +01:00
import TileLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import * as olProj from "ol/proj"
import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
2023-11-21 12:53:58 +01:00
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import Stroke from 'ol/style/Stroke'
import Fill from 'ol/style/Fill'
import CircleStyle from 'ol/style/Circle'
import Style from 'ol/style/Style'
import BaseEvent from 'ol/events/Event'
import {click} from 'ol/events/condition'
2023-11-21 12:53:58 +01:00
type StylesInterface = Record<string, Style>
2023-11-13 17:32:12 +01:00
export default class Conquer {
private conquerContainer: HTMLDivElement
private map: Map
private currentLongitude: number
private currentLatitude: number
private rotationOffset = 0
private heading = 0
2023-11-13 17:32:12 +01:00
private disableSetRotationOffset = false
private conquerLogin: HTMLDivElement
private conquerLoginGoToRegister: HTMLAnchorElement
2023-11-19 19:26:59 +01:00
private conquerLoginError: HTMLParagraphElement
private conquerLoginSuccess: HTMLParagraphElement
2023-11-19 23:14:02 +01:00
private conquerLoginUsername: HTMLInputElement
private conquerLoginPassword: HTMLInputElement
private conquerLoginSubmit: HTMLButtonElement
2023-11-17 23:16:54 +01:00
private conquerRegisterGoToLogin: HTMLAnchorElement
private conquerRegister: HTMLDivElement
2023-11-19 19:26:59 +01:00
private conquerRegisterUsername: HTMLInputElement
private conquerRegisterPassword: HTMLInputElement
private conquerRegisterRepeatPassword: HTMLInputElement
private conquerRegisterSubmit: HTMLButtonElement
private conquerRegisterError: HTMLParagraphElement
private currentPositionFeature: Feature | null
2023-11-21 12:53:58 +01:00
private vectorLayer: VectorLayer<VectorSource> | null = null
private alpha = 0
private beta = 0
private gamma = 0
2023-11-13 17:32:12 +01:00
static start() {
const conquerContainer = document.querySelector(".conquer-container")
2023-11-13 17:32:12 +01:00
if (conquerContainer !== null) {
if (!(conquerContainer instanceof HTMLDivElement)) {
console.error(".conquer-container is not a div.")
return
2023-11-13 17:32:12 +01:00
}
const conquer = new Conquer(conquerContainer)
conquer.run()
2023-11-13 17:32:12 +01:00
}
}
setCenterDisplaced(lat: number, lon: number) {
const olCoordinates = this.realCoordinatesToOl(lat, lon)
const size = this.map.getSize()
if (size === undefined) {
return
}
this.map.getView().centerOn(olCoordinates, size, [size[0]/2, size[1]-60])
}
static fail(error: string): never {
alert('Error de interfaz')
throw new Error(error)
}
fillConquerLogin() {
const conquerLogin = document.querySelector('.conquer-login')
if (conquerLogin === null || !(conquerLogin instanceof HTMLDivElement)) {
Conquer.fail('conquerLogin is invalid')
}
this.conquerLogin = conquerLogin
const conquerLoginGoToRegister = document.querySelector('.conquer-login-go-to-register')
if (conquerLoginGoToRegister === null || !(conquerLoginGoToRegister instanceof HTMLAnchorElement)) {
Conquer.fail('Link to go to register from login is invalid.')
}
this.conquerLoginGoToRegister = conquerLoginGoToRegister
this.conquerLoginGoToRegister.addEventListener('click', () => {
this.goToRegister()
})
2023-11-19 19:26:59 +01:00
const conquerLoginError = document.querySelector('.conquer-login-error')
if (conquerLoginError === null || !(conquerLoginError instanceof HTMLParagraphElement)) {
Conquer.fail('Unable to find conquer login error.')
}
this.conquerLoginError = conquerLoginError
const conquerLoginSuccess = document.querySelector('.conquer-login-success')
if (conquerLoginSuccess === null || !(conquerLoginSuccess instanceof HTMLParagraphElement)) {
Conquer.fail('Unable to find conquer login success.')
}
this.conquerLoginSuccess = conquerLoginSuccess
2023-11-19 23:14:02 +01:00
const conquerLoginUsername = document.querySelector('.conquer-login-username')
if (conquerLoginUsername === null || !(conquerLoginUsername instanceof HTMLInputElement)) {
Conquer.fail('Unable to find conquer login username field.')
}
this.conquerLoginUsername = conquerLoginUsername
const conquerLoginPassword = document.querySelector('.conquer-login-password')
if (conquerLoginPassword === null || !(conquerLoginPassword instanceof HTMLInputElement)) {
Conquer.fail('Unable to find conquer login password field.')
}
this.conquerLoginPassword = conquerLoginPassword
const conquerLoginSubmit = document.querySelector('.conquer-login-submit')
if (conquerLoginSubmit === null || !(conquerLoginSubmit instanceof HTMLButtonElement)) {
Conquer.fail('Unable to find the submit button for the login.')
}
this.conquerLoginSubmit = conquerLoginSubmit
2023-11-20 20:20:04 +01:00
this.conquerLoginSubmit.addEventListener('click', (event: Event) => {
event.preventDefault()
2023-11-19 23:14:02 +01:00
this.onLoginRequested()
})
}
2023-11-19 23:14:02 +01:00
async onLoginRequested(): Promise<void> {
const username = this.conquerLoginUsername.value
const password = this.conquerLoginPassword.value
2023-11-19 23:14:02 +01:00
const urlUser = new URL('/conquer/user/login', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
let responseJson
let status
try {
const response = await fetch(urlUser, {
method: 'POST',
body: JSON.stringify({
username: username,
password: password,
})
})
responseJson = await response.json()
status = response.status
} catch(e) {
console.error(e)
this.addNewLoginError('El servidor ha enviado datos inesperados.')
return
2023-11-19 23:14:02 +01:00
}
if (status !== 200) {
this.addNewLoginError(responseJson.error)
return
}
this.unsetLoginAndRegisterErrors()
const isLogged = await this.isLogged()
2023-11-20 20:20:04 +01:00
if (isLogged) {
2023-11-21 12:53:58 +01:00
this.onLoginSuccess()
}
}
async onLoginSuccess(): Promise<void> {
await this.removeLoginRegisterCombo()
const currentPositionFeature = this.currentPositionFeature
if (currentPositionFeature === null) {
return
}
this.map.on('click', (event: MapEvent) => {
this.onClickMap(event)
})
}
async onClickMap(event: MapEvent): Promise<void> {
if (this.vectorLayer === null) {
return
}
if (!(event instanceof MapBrowserEvent)) {
return
}
if (event.dragging) {
return
}
const pixel = event.pixel
const features = this.map.getFeaturesAtPixel(pixel)
const feature = features.length ? features[0] : undefined
if (feature === undefined) {
return
}
if (!(feature instanceof Feature)) {
return
}
this.onClickFeature(feature)
}
async onClickSelf(): Promise<void> {
alert('Pulsaste en ti mismo')
}
private isFeatureEnabledMap: Record<string, boolean> = {}
async onClickFeature(feature: Feature): Promise<void> {
if (this.isFeatureEnabledMap[feature.getProperties().type] === undefined) {
this.isFeatureEnabledMap[feature.getProperties().type] = true
}
if (!this.isFeatureEnabledMap[feature.getProperties().type]) {
return
}
this.isFeatureEnabledMap[feature.getProperties().type] = false
window.setTimeout(() => {
this.isFeatureEnabledMap[feature.getProperties().type] = true
}, 100);
if (feature === this.currentPositionFeature) {
this.onClickSelf()
return
2023-11-20 20:20:04 +01:00
}
2023-11-19 23:14:02 +01:00
}
async goToRegister(): Promise<void> {
const isLogged = await this.isLogged()
await this.removeLoginRegisterCombo()
if (!isLogged) {
this.conquerRegister.classList.remove('conquer-display-none')
}
}
2023-11-17 23:16:54 +01:00
async goToLogin(): Promise<void> {
const isLogged = await this.isLogged()
2023-11-17 23:16:54 +01:00
await this.removeLoginRegisterCombo()
if (!isLogged) {
this.conquerLogin.classList.remove('conquer-display-none')
}
}
async removeLoginRegisterCombo(): Promise<void> {
this.conquerLogin.classList.add('conquer-display-none')
this.conquerRegister.classList.add('conquer-display-none')
}
fillConquerRegister() {
const conquerRegister = document.querySelector('.conquer-register')
if (conquerRegister === null || !(conquerRegister instanceof HTMLDivElement)) {
Conquer.fail('conquerRegister is invalid')
}
this.conquerRegister = conquerRegister
2023-11-17 23:16:54 +01:00
const conquerRegisterGoToLogin = document.querySelector('.conquer-register-go-to-login')
if (conquerRegisterGoToLogin === null || !(conquerRegisterGoToLogin instanceof HTMLAnchorElement)) {
Conquer.fail('Link to go to login from register is invalid.')
}
this.conquerRegisterGoToLogin = conquerRegisterGoToLogin
this.conquerRegisterGoToLogin.addEventListener('click', () => {
this.goToLogin()
})
2023-11-19 19:26:59 +01:00
const conquerRegisterUsername = document.querySelector('.conquer-register-username')
if (conquerRegisterUsername === null || !(conquerRegisterUsername instanceof HTMLInputElement)) {
Conquer.fail('No username field in conquer register.')
}
this.conquerRegisterUsername = conquerRegisterUsername
const conquerRegisterPassword = document.querySelector('.conquer-register-password')
if (conquerRegisterPassword === null || !(conquerRegisterPassword instanceof HTMLInputElement)) {
Conquer.fail('No password field in conquer register.')
}
this.conquerRegisterPassword = conquerRegisterPassword
const conquerRegisterRepeatPassword = document.querySelector('.conquer-register-repeat-password')
if (conquerRegisterRepeatPassword === null || !(conquerRegisterRepeatPassword instanceof HTMLInputElement)) {
Conquer.fail('No repeat password field in conquer register.')
}
this.conquerRegisterRepeatPassword = conquerRegisterRepeatPassword
const conquerRegisterSubmit = document.querySelector('.conquer-register-submit')
if (conquerRegisterSubmit === null || !(conquerRegisterSubmit instanceof HTMLButtonElement)) {
Conquer.fail('No register submit button found.')
}
this.conquerRegisterSubmit = conquerRegisterSubmit
this.conquerRegisterSubmit.addEventListener('click', (event: Event) => {
event.preventDefault()
this.onRegisterRequest()
})
const conquerRegisterError = document.querySelector('.conquer-register-error')
if (conquerRegisterError === null || !(conquerRegisterError instanceof HTMLParagraphElement)) {
Conquer.fail('Unable to find the conquer error element.')
}
this.conquerRegisterError = conquerRegisterError
}
unsetLoginAndRegisterErrors() {
this.conquerRegisterError.classList.add('conquer-display-none')
this.conquerLoginError.classList.add('conquer-display-none')
}
async onRegisterRequest(): Promise<void> {
const username = this.conquerRegisterUsername.value
const password = this.conquerRegisterPassword.value
const repeatPassword = this.conquerRegisterRepeatPassword.value
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
let responseJson
2023-11-19 23:14:02 +01:00
let status
2023-11-19 19:26:59 +01:00
try {
2023-11-19 23:14:02 +01:00
const response = await fetch(urlUser, {
method: 'PUT',
body: JSON.stringify({
username: username,
password: password,
repeat_password: repeatPassword
})
})
2023-11-19 19:26:59 +01:00
responseJson = await response.json()
2023-11-19 23:14:02 +01:00
status = response.status
2023-11-19 19:26:59 +01:00
} catch(e) {
console.error(e)
2023-11-19 23:14:02 +01:00
this.addNewRegisterError('El servidor ha enviado datos inesperados.')
return
2023-11-19 19:26:59 +01:00
}
2023-11-19 23:14:02 +01:00
if (status !== 200) {
2023-11-19 19:26:59 +01:00
this.addNewRegisterError(responseJson.error)
return
}
this.addNewLoginSuccessText(`Usuario registrado ${username}.`)
this.goToLogin()
}
addNewLoginSuccessText(message: string): void {
this.unsetLoginAndRegisterErrors()
this.conquerLoginSuccess.innerText = message
this.conquerLoginSuccess.classList.remove('conquer-display-none')
}
2023-11-19 23:14:02 +01:00
addNewLoginError(error: string): void {
this.unsetLoginAndRegisterErrors()
this.conquerLoginSuccess.classList.add('conquer-display-none')
this.conquerLoginError.innerText = error
this.conquerLoginError.classList.remove('conquer-display-none')
}
2023-11-19 19:26:59 +01:00
addNewRegisterError(error: string): void {
this.unsetLoginAndRegisterErrors()
this.conquerLoginSuccess.classList.add('conquer-display-none')
this.conquerRegisterError.innerText = error
this.conquerRegisterError.classList.remove('conquer-display-none')
}
2023-11-21 12:53:58 +01:00
async checkLogin(): Promise<void> {
const isLogged = await this.isLogged()
2023-11-13 17:32:12 +01:00
if (!isLogged) {
this.conquerLogin.classList.remove('conquer-display-none')
2023-11-21 12:53:58 +01:00
return
2023-11-13 17:32:12 +01:00
}
2023-11-21 12:53:58 +01:00
this.onLoginSuccess()
}
async run() {
this.fillConquerLogin()
this.fillConquerRegister()
this.checkLogin()
const conquerContainer = this.conquerContainer
2023-11-13 17:32:12 +01:00
//layer.on('prerender', (evt) => {
// // return
// if (evt.context) {
// const context = evt.context as CanvasRenderingContext2D
// context.filter = 'grayscale(80%) invert(100%) '
// context.globalCompositeOperation = 'source-over'
2023-11-13 17:32:12 +01:00
// }
//})
2023-11-13 17:32:12 +01:00
//layer.on('postrender', (evt) => {
// if (evt.context) {
// const context = evt.context as CanvasRenderingContext2D
// context.filter = 'none'
2023-11-13 17:32:12 +01:00
// }
//})
olProj.useGeographic()
2023-11-13 17:32:12 +01:00
this.map = new Map({
target: conquerContainer,
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
zoom: 21,
maxZoom: 22,
}),
})
this.setLocationChangeTriggers()
this.setRotationChangeTriggers()
}
setRotationChangeTriggers(): void {
if (window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", (event) => {
if (event.alpha !== null && event.beta !== null && event.gamma !== null) {
this.onRotate(event.alpha, event.beta, event.gamma)
}
}, true)
}
}
2023-11-21 12:53:58 +01:00
addCurrentLocationMarkerToMap(currentLatitude: number,
currentLongitude: number) {
const currentPositionFeature = new Feature({
type: 'currentPositionFeature',
geometry: new Point(this.realCoordinatesToOl(currentLatitude, currentLongitude))
})
this.currentPositionFeature = currentPositionFeature
}
processLocation(location: GeolocationPosition) {
this.currentLatitude = location.coords.latitude
this.currentLongitude = location. coords.longitude
if (location.coords.heading !== null && (this.alpha != 0 || this.beta != 0 || this.gamma != 0) && !this.disableSetRotationOffset) {
this.disableSetRotationOffset = true
this.heading = location.coords.heading
this.rotationOffset = this.compassHeading(this.alpha, this.beta, this.gamma) + (location.coords.heading*Math.PI*2)/360
2023-11-21 12:53:58 +01:00
}
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
this.addCurrentLocationMarkerToMap(this.currentLatitude, this.currentLongitude)
this.refreshLayers()
}
private refreshLayers(): void {
if (this.currentPositionFeature === null) {
return
}
const styles: StylesInterface = {
currentPositionFeature: new Style({
image: new CircleStyle({
radius: 14,
fill: new Fill({color: 'white'}),
stroke: new Stroke({
color: 'blue',
width: 2,
})
})
})
2023-11-21 12:53:58 +01:00
};
const vectorLayer = new VectorLayer<VectorSource>({
source: new VectorSource({
features: [this.currentPositionFeature]
}),
})
if (this.vectorLayer !== null) {
this.map.removeLayer(this.vectorLayer)
2023-11-17 23:16:54 +01:00
}
2023-11-21 12:53:58 +01:00
vectorLayer.setStyle((feature) => {
return styles[feature.getProperties().type]
})
this.map.addLayer(vectorLayer)
this.vectorLayer = vectorLayer
this.map.on('click', (event: MapEvent) => {
this.onClickMap(event)
})
}
setLocationChangeTriggers(): void {
2023-11-13 17:32:12 +01:00
window.setInterval(() => {
this.disableSetRotationOffset = false
}, 10000)
this.currentPositionFeature = null
2023-11-13 17:32:12 +01:00
window.setTimeout(() => {
window.setInterval(() => {
navigator.geolocation.getCurrentPosition((location) => {
this.processLocation(location)
2023-11-13 17:32:12 +01:00
}, () => {
2023-11-17 23:16:54 +01:00
return
2023-11-13 17:32:12 +01:00
}, {
2023-11-17 23:16:54 +01:00
enableHighAccuracy: true,
2023-11-13 17:32:12 +01:00
})
2023-11-21 12:53:58 +01:00
}, 3000)
2023-11-13 17:32:12 +01:00
}, 1000)
2023-11-21 12:53:58 +01:00
const initialLatitude = 37.58237
const initialLongitude = -5.96766
this.setCenterDisplaced(initialLatitude, initialLongitude)
this.addCurrentLocationMarkerToMap(initialLatitude, initialLongitude)
this.refreshLayers()
navigator.geolocation.watchPosition((location) => {
this.processLocation(location)
}, (err) => {
return
}, {
enableHighAccuracy: true,
})
2023-11-13 17:32:12 +01:00
}
realCoordinatesToOl(lat: number, lon: number): number[] {
return olProj.transform(
[lon, lat],
new Projection({ code: "WGS84" }),
new Projection({ code: "EPSG:900913" }),
)
2023-11-13 17:32:12 +01:00
}
compassHeading(alpha:number, beta:number, gamma:number): number {
const alphaRad = alpha * (Math.PI / 180)
return alphaRad
2023-11-13 17:32:12 +01:00
}
logToServer(logValue: string) {
const urlLog = new URL('/conquer/log', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
urlLog.searchParams.append('log', logValue)
fetch(urlLog).then(() => {
return
}).catch((error) => {
console.error(error)
})
}
enabledOnRotate = true
2023-11-13 17:32:12 +01:00
onRotate(alpha: number, beta: number, gamma: number) {
if (this.enabledOnRotate) {
this.alpha = alpha
this.beta = beta
this.gamma = gamma
this.enabledOnRotate = false
this.map.getView().setRotation((this.compassHeading(alpha, beta, gamma) - this.rotationOffset))
2023-11-13 17:32:12 +01:00
window.setTimeout(() => {
this.enabledOnRotate = true
}, 10)
}
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
}
constructor(conquerContainer: HTMLDivElement) {
this.conquerContainer = conquerContainer
2023-11-13 17:32:12 +01:00
}
2023-11-20 20:20:04 +01:00
private async addNewLoginRegisterError(message: string): Promise<void> {
this.addNewRegisterError(message)
this.addNewLoginError(message)
}
2023-11-13 17:32:12 +01:00
private async isLogged(): Promise<boolean> {
2023-11-20 20:20:04 +01:00
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
let responseJson
2023-11-20 20:20:04 +01:00
let status
try {
const response = await fetch(urlUser)
status = response.status
} catch {
this.addNewLoginRegisterError('Error del servidor')
return false
}
return status === 200
2023-11-13 17:32:12 +01:00
}
}