Adding initial create node support.

This commit is contained in:
Sergiotarxz 2023-11-28 21:10:12 +01:00
parent e5d9230a74
commit acec248f4d
11 changed files with 555 additions and 23 deletions

View File

@ -0,0 +1,67 @@
import Conquer from '@burguillosinfo/conquer'
import MapState from '@burguillosinfo/conquer/map-state'
export default class CreateNode {
private conquer: Conquer
private createNodeSlide: HTMLElement
constructor(conquer: Conquer) {
this.conquer = conquer
this.getCreateNodeCancel().addEventListener('click', () => {
this.conquer.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
this.conquer.removeState(MapState.CREATE_NODE)
this.conquer.addState(MapState.NORMAL)
})
this.getCreateNodeNewNodeElement().addEventListener('click', () => {
const state = this.conquer.getState()
if (state & MapState.SELECT_WHERE_TO_CREATE_NODE) {
this.conquer.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
return
}
this.conquer.addState(MapState.SELECT_WHERE_TO_CREATE_NODE)
})
}
private getCreateNodeCancel(): HTMLElement {
const createNodeCancel = document.querySelector('#create-node-exit')
if (createNodeCancel === null || !(createNodeCancel instanceof HTMLElement)) {
Conquer.fail('Unable to find #create-node-exit.')
}
return createNodeCancel
}
private getCreateNodeNewNodeElement(): HTMLElement {
const createNodeNewElement = document.querySelector('#create-node-new-node')
if (createNodeNewElement === null || !(createNodeNewElement instanceof HTMLElement)) {
Conquer.fail('Unable to find #create-node-slide.')
}
return createNodeNewElement
}
private getCreateNodeSlide(): HTMLElement {
const createNodeSlide = document.querySelector('#create-node-slide')
if (createNodeSlide === null || !(createNodeSlide instanceof HTMLElement)) {
Conquer.fail('Unable to find #create-node-slide.')
}
return createNodeSlide
}
public refreshState() {
if (!(this.conquer.getState() & MapState.CREATE_NODE)) {
this.getCreateNodeSlide().classList.add('conquer-display-none')
return
}
this.refreshCreateNodeNewNodeState()
this.getCreateNodeSlide().classList.remove('conquer-display-none')
}
private refreshCreateNodeNewNodeState(): void {
const createNodeNewNode = this.getCreateNodeNewNodeElement()
if (this.conquer.getState() & MapState.SELECT_WHERE_TO_CREATE_NODE) {
createNodeNewNode.innerText = 'Cancelar.'
} else {
createNodeNewNode.innerText = 'Crear nodo.'
}
}
}

View File

@ -14,12 +14,15 @@ 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'
import ConquerLogin from '@burguillosinfo/conquer/login'
import InterfaceManager from '@burguillosinfo/conquer/interface-manager'
import SelfPlayerUI from '@burguillosinfo/conquer/interface/self-player'
import CreateNode from '@burguillosinfo/conquer/create-node'
import MapState from '@burguillosinfo/conquer/map-state'
import MapNode from '@burguillosinfo/conquer/map-node'
type StylesInterface = Record<string, Style>
export default class Conquer {
private conquerContainer: HTMLDivElement
private map: Map
@ -34,32 +37,105 @@ export default class Conquer {
private beta = 0
private gamma = 0
private conquerLogin: ConquerLogin
private selfPlayerUI: SelfPlayerUI | null = null
private interfaceManager: InterfaceManager
private firstSetCenter = true
private firstSetRotation = true
private state: MapState = MapState.NOTHING
private createNodeObject: CreateNode
private serverNodes: Record<string, MapNode> = {}
public getServerNodes(): Record<string, MapNode> {
return this.serverNodes
}
public getState(): MapState {
return this.state
}
public setState(state: MapState) {
this.state = state
this.refreshState()
}
public removeState(state: MapState) {
this.state &= ~state
this.refreshState()
}
public addState(state: MapState) {
this.state |= state
this.refreshState()
}
private refreshState(): void {
this.createNodeObject.refreshState()
return
}
static start() {
const conquerContainer = document.querySelector(".conquer-container")
if (conquerContainer !== null) {
if (!(conquerContainer instanceof HTMLDivElement)) {
console.error(".conquer-container is not a div.")
return
}
const conquer = new Conquer(conquerContainer)
conquer.run()
if (conquerContainer === null || !(conquerContainer instanceof HTMLDivElement)) {
Conquer.fail('.conquer-container is not a div.')
}
const conquer = new Conquer(conquerContainer)
conquer.run()
}
setCenterDisplaced(lat: number, lon: number) {
const olCoordinates = this.realCoordinatesToOl(lat, lon)
const size = this.map.getSize()
if (size === undefined) {
return
if (this.firstSetCenter || !(this.state & MapState.FREE_MOVE)) {
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])
this.firstSetCenter = false
}
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)
}
public isStateCreatingNode(): boolean {
return !!(this.getState() & MapState.CREATE_NODE)
}
public isStateSelectWhereToCreateNode(): boolean {
return !!(this.getState() & MapState.SELECT_WHERE_TO_CREATE_NODE)
}
private createNodeCounter = 0
async onClickWhereToCreateNode(event: MapEvent) {
if (!(event instanceof MapBrowserEvent)) {
return
}
const pixel = event.pixel
const coordinates = this.map.getCoordinateFromPixel(pixel)
const feature = new Feature({
geometry: new Point(coordinates)
})
console.log(coordinates)
const style = new Style({
image: new CircleStyle({
radius: 14,
fill: new Fill({color: 'white'}),
stroke: new Stroke({
color: 'gray',
width: 2,
})
})
})
const mapNode = new MapNode(style, feature, `server-node-${++this.createNodeCounter}`)
this.getServerNodes()[mapNode.getId()] = mapNode
this.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
this.refreshLayers()
}
async onClickMap(event: MapEvent): Promise<void> {
if (this.isStateCreatingNode() && this.isStateSelectWhereToCreateNode()) {
this.onClickWhereToCreateNode(event)
}
if (!(this.getState() & MapState.NORMAL)) {
return
}
if (this.vectorLayer === null) {
return
}
@ -82,8 +158,21 @@ export default class Conquer {
}
async onClickSelf(): Promise<void> {
alert('Pulsaste en ti mismo')
if (!(this.state & MapState.NORMAL)) {
return
}
const selfPlayerUI = new SelfPlayerUI()
selfPlayerUI.on('close', () => {
this.interfaceManager.remove(selfPlayerUI)
})
selfPlayerUI.on('createNodeStart', () => {
this.addState(MapState.CREATE_NODE)
this.removeState(MapState.NORMAL)
})
this.interfaceManager.push(selfPlayerUI)
this.selfPlayerUI = selfPlayerUI
}
private isFeatureEnabledMap: Record<string, boolean> = {}
async onClickFeature(feature: Feature): Promise<void> {
@ -113,14 +202,22 @@ export default class Conquer {
})
}
async run() {
private runPreStartState(): void {
const createNodeObject = new CreateNode(this)
this.createNodeObject = createNodeObject
const interfaceManager = new InterfaceManager()
this.interfaceManager = interfaceManager
const conquerLogin = new ConquerLogin(interfaceManager)
conquerLogin.on('login', () => {
this.onLoginSuccess()
})
conquerLogin.start()
this.conquerLogin = conquerLogin
}
async run() {
this.runPreStartState()
this.setState(MapState.NORMAL)
const conquerContainer = this.conquerContainer
//layer.on('prerender', (evt) => {
// // return
@ -202,9 +299,14 @@ export default class Conquer {
})
})
};
const features = [this.currentPositionFeature]
for (const key in this.getServerNodes()) {
styles[key] = this.getServerNodes()[key].getStyle()
features.push(this.getServerNodes()[key].getNode())
}
const vectorLayer = new VectorLayer<VectorSource>({
source: new VectorSource({
features: [this.currentPositionFeature]
features: features
}),
})
if (this.vectorLayer !== null) {
@ -235,8 +337,11 @@ export default class Conquer {
})
}, 3000)
}, 1000)
const initialLatitude = 37.58237
const initialLongitude = -5.96766
// const initialLatitude = 37.58237
//const initialLongitude = -5.96766
const initialLongitude = 2.500845037550267
const initialLatitude = 48.81050698635832
this.setCenterDisplaced(initialLatitude, initialLongitude)
this.addCurrentLocationMarkerToMap(initialLatitude, initialLongitude)
this.refreshLayers()
@ -276,7 +381,10 @@ export default class Conquer {
this.beta = beta
this.gamma = gamma
this.enabledOnRotate = false
this.map.getView().setRotation((this.compassHeading(alpha, beta, gamma) - this.rotationOffset))
if (this.firstSetRotation || !(this.state & MapState.FREE_ROTATION)) {
this.map.getView().setRotation((this.compassHeading(alpha, beta, gamma) - this.rotationOffset))
this.firstSetRotation = false
}
window.setTimeout(() => {

View File

@ -38,7 +38,7 @@ export default abstract class ConquerInterface {
this.callbacks[eventName].push(callback)
}
private runCallbacks(eventName: string) {
protected runCallbacks(eventName: string) {
const callbacks = this.callbacks[eventName];
if (callbacks === undefined) {
return

View File

@ -0,0 +1,78 @@
import ConquerInterface from '@burguillosinfo/conquer/interface'
import Conquer from '@burguillosinfo/conquer'
import ConquerUser from '@burguillosinfo/conquer/user'
export default class SelfPlayerUI extends ConquerInterface {
private selfPlayer: ConquerUser | null = null
private userWelcome: HTMLElement | null = null
protected generateNodes(): HTMLElement[] {
const player = this.getNodeFromTemplateId('conquer-self-player-template')
return [player]
}
public getSelfPlayerNode(): HTMLElement {
return this.getNodes()[0]
}
public getExitButton(): HTMLElement {
const maybeExitButton = this.getSelfPlayerNode().querySelector('.conquer-exit-button')
if (maybeExitButton === null || !(maybeExitButton instanceof HTMLElement)) {
Conquer.fail('No exit button.')
}
return maybeExitButton
}
public generateInterfaceElementCentered(): HTMLElement {
return this.getNodeFromTemplateId('conquer-interface-element-padded-template')
}
public async run(): Promise<void> {
const selfPlayerNode = this.getSelfPlayerNode()
selfPlayerNode.classList.remove('conquer-display-none')
const exitButton = this.getExitButton()
exitButton.addEventListener('click', () => {
this.runCallbacks('close')
})
const user = await ConquerUser.getSelfUser()
if (user === null) {
this.runCallbacks('close')
return
}
this.selfPlayer = user
this.populateWelcome()
this.populateCreateNodeOption()
}
private populateCreateNodeOption() {
if (!this.selfPlayer?.isAdmin()) {
return
}
const createNodeButton = document.createElement('button')
createNodeButton.innerText = 'Crear Nuevo Nodo'
createNodeButton.addEventListener('click', () => {
this.runCallbacks('createNodeStart')
// We close because it is a sensible thing to do.
this.runCallbacks('close')
})
const createNodeButtonInterface = this.generateInterfaceElementCentered()
createNodeButtonInterface.appendChild(createNodeButton)
this.getSelfPlayerNode().appendChild(createNodeButtonInterface)
}
private populateWelcome(): void {
const userWelcome = this.getUserWelcome()
const userWelcomeInterface = this.generateInterfaceElementCentered();
userWelcomeInterface.appendChild(userWelcome)
this.getSelfPlayerNode().appendChild(userWelcomeInterface)
}
private getUserWelcome(): HTMLElement {
if (this.userWelcome !== null) {
return this.userWelcome
}
const element = document.createElement('h2')
if (this.selfPlayer === null) {
throw new Error('User still not set')
}
element.innerText = `¡Hola, ${this.selfPlayer.getUsername()}!`
this.userWelcome = element
return this.userWelcome
}
}

View File

@ -0,0 +1,28 @@
import Style from 'ol/style/Style'
import Feature from 'ol/Feature'
export default class MapNode {
private style: Style
private node: Feature
private id: string
constructor(style: Style, node: Feature, id: string) {
this.style = style
this.node = node.clone()
this.id = id
this.node.setProperties({type: this.id})
}
public getId(): string {
return this.id
}
public getNode(): Feature {
return this.node
}
public getStyle(): Style {
return this.style
}
}

View File

@ -0,0 +1,10 @@
enum MapState {
NOTHING = 0x0,
NORMAL = 0x1,
FREE_MOVE = 0x2,
FREE_ROTATION = 0x4,
CREATE_NODE = 0x8,
SELECT_WHERE_TO_CREATE_NODE = 0x10,
}
export default MapState

58
js-src/conquer/user.ts Normal file
View File

@ -0,0 +1,58 @@
export interface UserData {
is_admin: number
kind: string
last_activity?: string
registration_date?: string
username: string
uuid: string
}
export default class ConquerUser {
private _isAdmin = false
private kind = "ConquerUser"
private lastActivity: string | null = null
private registrationDate: string | null = null
private username: string | null = null
private uuid: string | null = null
constructor(data: UserData) {
this.lastActivity = data.last_activity ?? null;
this.registrationDate = data.registration_date ?? null;
if (this.kind !== data.kind) {
throw new Error(`We cannot instance a user from a kind different to ${this.kind}.`)
}
this._isAdmin = !!data.is_admin || false
this.uuid = data.uuid
this.username = data.username
if (this.username === null || this.username === undefined) {
throw new Error('No username in user instance')
}
if (this.uuid === null || this.username === undefined) {
throw new Error('No uuid in user instance')
}
}
public static async getSelfUser(): Promise<ConquerUser | null> {
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
try {
const response = await fetch(urlUser)
if (response.status !== 200) {
throw new Error('Invalid response fetching user.')
}
const userData = await response.json()
return new ConquerUser(userData)
} catch (error) {
console.error(error)
return null
}
}
public getUsername(): string {
if (this.username === null) {
throw new Error('User username cannot be null.')
}
return this.username
}
public isAdmin(): boolean {
return this._isAdmin
}
}

View File

@ -16,8 +16,36 @@ body {
padding: 3px;
border-radius: 10px;
border: solid 1px black; }
body div.conquer-interface-element-padded {
width: calc(100% - 60px);
padding-left: 30px;
padding-right: 30px;
display: flex;
justify-content: center; }
body div.create-node-slide {
display: flex;
position: fixed;
z-index: 1;
bottom: 0;
height: 100px; }
body div.create-node-slide.conquer-display-none {
display: none; }
body p.conquer-login-success {
color: green; }
body a.conquer-exit-button {
color: white;
font-weight: bold;
text-decoration: none;
background: darkmagenta;
padding: 10px;
border-radius: 50%;
aspect-ratio: 1 / 1;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: xx-large;
border: 2px black solid; }
body div.conquer-overlay-transparent {
background: black;
opacity: 50%;
@ -27,6 +55,33 @@ body {
top: 0;
left: 0;
z-index: 1; }
body div.conquer-self-player {
border: 1px solid black;
position: fixed;
color: black;
font-size: 1.5rem;
border-radius: 30px;
background: darkseagreen;
top: 0;
left: 0;
width: calc(100% - 12px);
height: calc(100% - 22px);
margin: 5px;
margin-top: 10px;
margin-bottom: 10px; }
body div.conquer-top-bar {
display: flex;
width: calc(100% - 20px);
border-radius: 30px 30px 0 0;
padding-top: 20px;
padding-bottom: 20px;
padding-left: 10px;
padding-right: 10px;
background: darkcyan;
margin-left: 0;
border-bottom: 1px black solid;
display: flex;
justify-content: end; }
body div.conquer-login, body div.conquer-register {
border: 1px solid black;
position: fixed;

View File

@ -25,9 +25,41 @@ body {
border-radius: 10px;
border: solid 1px black;
}
div.conquer-interface-element-padded {
width: calc(100% - 60px);
padding-left: 30px;
padding-right: 30px;
display: flex;
justify-content: center;
}
div.create-node-slide {
display: flex;
position: fixed;
z-index: 1;
bottom: 0;
height: 100px;
&.conquer-display-none {
display: none;
}
}
p.conquer-login-success {
color: green;
}
a.conquer-exit-button {
color: white;
font-weight: bold;
text-decoration: none;
background: darkmagenta;
padding: 10px;
border-radius: 50%;
aspect-ratio: 1 / 1;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: xx-large;
border: 2px black solid;
}
div.conquer-overlay-transparent {
background: black;
opacity: 50%;
@ -38,6 +70,36 @@ body {
left: 0;
z-index: 1;
}
div.conquer-self-player {
border: 1px solid black;
position: fixed;
color: black;
font-size: 1.5rem;
border-radius: 30px;
background: darkseagreen;
top: 0;
left: 0;
width: calc(100% - 12px);
height: calc(100% - 22px);
margin: 5px;
margin-top: 10px;
margin-bottom: 10px;
}
div.conquer-top-bar {
display: flex;
width: calc(100% - 20px);
border-radius: 30px 30px 0 0;
padding-top: 20px;
padding-bottom: 20px;
padding-left: 10px;
padding-right: 10px;
background: darkcyan;
margin-left: 0;
border-bottom: 1px black solid;
display: flex;
justify-content: end;
}
div.conquer-login,div.conquer-register {
form {
width: 100%;

File diff suppressed because one or more lines are too long

View File

@ -5,11 +5,22 @@
<title>Conquista Burguillos</title>
<script src="/js/bundle.js?v=<%=$css_version%>"></script>
<link rel="stylesheet" href="/css/styles.css?v=<%=$css_version%>"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'/>
</head>
<body>
<div id="conquer-overlay-transparent-template" class="conquer-overlay-transparent conquer-display-none">
</div>
<div class="create-node-slide conquer-display-none" id="create-node-slide">
<button id="create-node-new-node">Autogenerado.</button>
<button id="create-node-exit">Salir del modo crear nodo.</button>
</div>
<div id="conquer-interface-element-padded-template" class="conquer-interface-element-padded">
</div>
<div id="conquer-self-player-template" class="conquer-self-player conquer-display-none">
<div class="conquer-top-bar">
<a href="#" class="conquer-exit-button">X</a>
</div>
</div>
<div id="conquer-login-template" class="conquer-login conquer-display-none">
<form>
<p class="conquer-login-error conquer-display-none"></p>