Compare commits
40 Commits
Author | SHA1 | Date |
---|---|---|
Sergiotarxz | a2999657a8 | |
Sergiotarxz | 003f4972cb | |
Sergiotarxz | 34723db31c | |
Sergiotarxz | 58c30c1059 | |
Sergiotarxz | 08f539bf42 | |
Sergiotarxz | c945cc453b | |
Sergiotarxz | 40e392e003 | |
Sergiotarxz | 278c7c5112 | |
Sergiotarxz | 389f325618 | |
Sergiotarxz | 16888b9fdb | |
Sergiotarxz | 5f70116da2 | |
Sergiotarxz | 30188a1a76 | |
Sergiotarxz | 35a65cfa1f | |
Sergiotarxz | e3708066e6 | |
Sergiotarxz | ed48de1c38 | |
Sergiotarxz | 589782365b | |
Sergiotarxz | 9279e6388a | |
Sergiotarxz | c38474614d | |
Sergiotarxz | 7994119d66 | |
Sergiotarxz | ac21ac1387 | |
Sergiotarxz | d9e6e664f2 | |
Sergiotarxz | 8c27095ad1 | |
Sergiotarxz | 85a104caa5 | |
Sergiotarxz | 24b4f7db9f | |
Sergiotarxz | 711f1dc845 | |
Sergiotarxz | 598dda2aae | |
Sergiotarxz | acec248f4d | |
Sergiotarxz | e5d9230a74 | |
Sergiotarxz | d4927e2e1b | |
Sergiotarxz | d73ff6692a | |
Sergiotarxz | 90d85ed4af | |
Sergiotarxz | 064ec75ed3 | |
Sergiotarxz | f3f111060b | |
Sergiotarxz | d6d827fe8d | |
Sergiotarxz | 1447b2fa6e | |
Sergiotarxz | 61b0066f0a | |
Sergiotarxz | 3396c36529 | |
Sergiotarxz | 21d9f46d03 | |
Sergiotarxz | 2d1430ca87 | |
Sergiotarxz | dd2ca2f786 |
|
@ -1,3 +0,0 @@
|
|||
[submodule "conversejs"]
|
||||
path = conversejs
|
||||
url = https://github.com/conversejs/converse.js/
|
7
Build.PL
|
@ -27,6 +27,13 @@ my $build = Module::Build->new(
|
|||
'Module::Pluggable' => 0,
|
||||
'List::AllUtils' => 0,
|
||||
'Lingua::Stem::Snowball' => 0,
|
||||
'Mojo::Redis' => 0,
|
||||
'DBIx::Class' => 0,
|
||||
'UUID::URandom' => 0,
|
||||
'Crypt::Bcrypt' => 0,
|
||||
'DBIx::Class::TimeStamp' => 0,
|
||||
'DateTime::Format::HTTP' => 0,
|
||||
'GIS::Distance' => 0,
|
||||
},
|
||||
);
|
||||
$build->create_build_script;
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<category>
|
||||
<title>Dentistas en Burguillos.</title>
|
||||
<description>
|
||||
<p>Te traemos una lista de dentistas en Burguillos para que puedas cuidar de la salud y estética de tu boca con los mejores profesionales.</p>
|
||||
<p>¿Quieres que tu comercio aparezca aquí? Contacta con <a href="mailto:contact@owlcode.tech">contact@owlcode.tech</a></p>
|
||||
|
||||
<p><a rel="noreferrer nofollow" href="https://www.freepik.es/vector-gratis/mujer-dentista-examinando-dientes-paciente-sobre-fondo-blanco_24553482.htm">Imagen de preview cortesía de Brgfx</a>.</p>
|
||||
</description>
|
||||
<priority>2</priority>
|
||||
<img src="/img/dentista.webp" bottom-preview="530"/>
|
||||
<menu_text>Dentistas</menu_text>
|
||||
<slug>dentistas</slug>
|
||||
<parent>comercios</parent>
|
||||
</category>
|
|
@ -3,7 +3,7 @@
|
|||
<date>2022-11-19T18:03+00:00</date>
|
||||
<title>Centro Médico Juan Manuel Pérez Sanchez - Datos de Contacto - Pedir Cita</title>
|
||||
<ogdesc>Centro Médico Juan Manuel Pérez Sanchez - Datos de Contacto - Pedir Cita</ogdesc>
|
||||
<last_modification_date>2023-05-03T00:59+00:00</last_modification_date>
|
||||
<last_modification_date>2023-05-03T00:59+00:00</last_modification_date>
|
||||
<category>comercios</category>
|
||||
<slug>centro-medico-juan-manuel-perez-sanchez</slug>
|
||||
<img src="/img/policlinica-burguillos-preview.webp"/>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<p>La clínica Juan Manuel Pérez Sanchez es un centro de atención a la salud dedicado a las siguientes especialidades:</p>
|
||||
|
||||
<ul>
|
||||
<li>Odontologia general, Ortodoncia, Ortodoncia Invisible, Odontopediatría, Estetica Dental y Labios, Protesis y Aparatos Dentales, Implantes...</li>
|
||||
<li>Odontologia general, Ortodoncia, Ortodoncia Invisible, Odontopediatria, Estetica Dental y Labios, Protesis y Aparatos Dentales, Implantes...</li>
|
||||
<li>Clínica concertada del plan de atención infantil de la Junta de Andalucía.</li>
|
||||
<li>Radiografías Panorex-Teleradiografía.</li>
|
||||
<li>Pedagogia, Especialista en trastornos de lenguaje y audicion, Clases de apoyo, Talleres, Tramitacion de becas escolares.</li>
|
||||
|
|
|
@ -27,109 +27,400 @@
|
|||
<a href="tel:+34621210460">621 210 460</a>.</p>
|
||||
<p>A continuación procedemos a dejar la carta para que podáis
|
||||
realizar el pedido que deseeis:</p>
|
||||
<details>
|
||||
<summary><h2>Entrantes</h2></summary>
|
||||
<ul>
|
||||
<li>Ensaladilla -- Tapa: <b>2.50€</b> 1/2 Ración: <b>5.00€</b> Ración: <b>10.00€</b></li>
|
||||
<li>Aliño de Pimientos -- Tapa: <b>2.50€</b> 1/2 Ración: <b>5.00€</b> Ración: <b>10.00€</b></li>
|
||||
<li>Aliño de Pulpo -- Tapa: <b>2.50€</b> 1/2 Ración: <b>5.00€</b> Ración: <b>10.00€</b></li>
|
||||
<li>Aliño de Huevas -- Tapa: <b>2.50€</b> 1/2 Ración: <b>5.00€</b> Ración: <b>10.00€</b></li>
|
||||
<li>Huevas con Mayonesa -- Tapa: <b>2.50€</b> 1/2 Ración: <b>5.00€</b> Ración: <b>10.00€</b></li>
|
||||
<li>Ensalada Mixta -- Ración: <b>4.00€</b></li>
|
||||
<li>Ensalada Normal -- Ración: <b>3.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Aperitivos</h2></summary>
|
||||
<ul>
|
||||
<li>Papas Bravas -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Papas Alioli Calientes -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Papas Alioli Frías -- Tapa: <b>2.50€</b> 1/2 Ración: <b>5.00€</b> Ración: <b>10.00€</b></li>
|
||||
<li>Croquetas de Jamón -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Croquetas de Cola de Toro -- Tapa: <b>3.50€</b> 1/2 Ración: <b>7.00€</b> Ración: <b>14.00€</b></li>
|
||||
<li>Nugget de Pollo -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Cachopo -- Ración: <b>8.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>En Temporada</h2></summary>
|
||||
<ul>
|
||||
<li>Cabrillas -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Caracoles -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Ibéricos</h2></summary>
|
||||
<ul>
|
||||
<li>Secreto -- Ración: <b>S-P</b></li>
|
||||
<li>Lagrimitas -- Tapa: <b>3.50€</b> 1/2 Ración: <b>7.00€</b> Ración: <b>14.00€</b></li>
|
||||
<li>Lagarto -- Tapa: <b>3.50€</b> 1/2 Ración: <b>7.00€</b> Ración: <b>14.00€</b></li>
|
||||
<li>Tocinito -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Carnes</h2></summary>
|
||||
<ul>
|
||||
<li>Carne Asá -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Solomillo al Whisky -- Tapa: <b>3.50€</b> 1/2 Ración: <b>8.00€</b> Ración: <b>16.00€</b></li>
|
||||
<li>Solomillo a la Pimienta -- Tapa: <b>3.50€</b> 1/2 Ración: <b>8.00€</b> Ración: <b>16.00€</b></li>
|
||||
<li>Solomillo al Roquefort -- Tapa: <b>3.50€</b> 1/2 Ración: <b>8.00€</b> Ración: <b>16.00€</b></li>
|
||||
<li>Churrasco de Pollo/Cerdo -- Ración: <b>5.50€</b></li>
|
||||
<li>Mini Serranito de Pollo/Cerdo -- Ración: <b>3.00€</b></li>
|
||||
<li>Serranito de Pollo/Cerdo -- Ración: <b>5.00€</b></li>
|
||||
<li>Pechuga de Pollo -- Ración: <b>5.50€</b></li>
|
||||
<li>Pinchito de Pollo/Cerdo -- Ración: <b>3.00€</b></li>
|
||||
<li>Brocheta de Solomillo -- Ración: <b>7.00€</b></li>
|
||||
<li>Hamburguesa Simple -- Ración: <b>2.50€</b></li>
|
||||
<li>Hamburguesa Completa -- Ración: <b>3.00€</b></li>
|
||||
<li>Hamburguesa de Buey -- Ración: <b>5.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Montaditos</h2></summary>
|
||||
<ul>
|
||||
<li>Montadito de Pollo/Cerdo -- Precio: <b>2.50€</b></li>
|
||||
<li>Mantecadito de Pollo/Cerdo -- Precio: <b>3.00€</b></li>
|
||||
<li>Montadito de Gambas -- Precio: <b>3.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Cazuelitas</h2></summary>
|
||||
<ul>
|
||||
<li>Carne con tomate -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Carrillada Ibérica -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Atún Encebollado -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Bacalao con Tomate -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Espinacas -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Pescados</h2></summary>
|
||||
<ul>
|
||||
<li>Chipirón a la Plancha/Frito -- Tapa: <b>3.50€</b> 1/2 Ración: <b>8.00€</b> Ración: <b>16.00€</b></li>
|
||||
<li>Calamares Fritos -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Choco Frito -- Tapa: <b>3.50€</b> 1/2 Ración: <b>7.00€</b> Ración: <b>14.00€</b></li>
|
||||
<li>Boquerones -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Acedias -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Puntillitas -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Adobo -- Tapa: <b>3.00€</b> 1/2 Ración: <b>6.00€</b> Ración: <b>12.00€</b></li>
|
||||
<li>Brocheta de Gambas -- Tapa: <b>3.00€</b> 1/2 Ración: <b>8.00€</b> Ración: <b>14.00€</b></li>
|
||||
<li>Pescado Variado -- 1/2 Ración: <b>7.00€</b> Ración: <b>14.00€</b></li>
|
||||
<li>Pez Espada -- Ración: <b>8.00€</b></li>
|
||||
<li>Merluza -- Ración: <b>6.00€</b></li>
|
||||
<li>Dorada -- Ración: <b>6.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><h2>Postres</h2></summary>
|
||||
<ol>
|
||||
<li>Arroz con leche <b>2.50€</b></li>
|
||||
<li>Natillas <b>2.50€</b></li>
|
||||
<li>Flan de Huevo <b>2.50€</b></li>
|
||||
<li>Helados Nestle <b>3.00€</b></li>
|
||||
</ol>
|
||||
</details>
|
||||
<h2>Entrantes</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ensaladilla</td>
|
||||
<td>2.50€</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aliño de Pimientos</td>
|
||||
<td>2.50€</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aliño de Pulpo</td>
|
||||
<td>2.50€</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aliño de Huevas</td>
|
||||
<td>2.50€</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Huevas con Mayonesa</td>
|
||||
<td>2.50€</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ensalada Mixta</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>4.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ensalada Normal</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Aperitivos</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Papas Bravas</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Papas Alioli Calientes</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Papas Alioli Frías</td>
|
||||
<td>2.50€</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Croquetas de Jamón</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Croquetas de Cola de Toro</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nugget de Pollo</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cachopo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>En Temporada</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cabrillas</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Caracoles</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Ibéricos</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Secreto</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>S-P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lagrimitas</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lagarto</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tocinito</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Carnes</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Carne Asá</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solomillo al Whisky</td>
|
||||
<td>3.50€</td>
|
||||
<td>8.00€</td>
|
||||
<td>16.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solomillo a la Pimienta</td>
|
||||
<td>3.50€</td>
|
||||
<td>8.00€</td>
|
||||
<td>16.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solomillo al Roquefort</td>
|
||||
<td>3.50€</td>
|
||||
<td>8.00€</td>
|
||||
<td>16.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Churrasco de Pollo/Cerdo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>5.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mini Serranito de Pollo/Cerdo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Serranito de Pollo/Cerdo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>5.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pechuga de Pollo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>5.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pinchito de Pollo/Cerdo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Brocheta de Solomillo</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>7.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hamburguesa Simple</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>2.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hamburguesa Completa</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hamburguesa de Buey</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>5.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Montaditos</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Precio</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Montadito de Pollo/Cerdo</td>
|
||||
<td>2.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mantecadito de Pollo/Cerdo</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Montadito de Gambas</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Cazuelitas</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Carne con tomate</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Carrillada Ibérica</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Atún Encebollado</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bacalao con Tomate</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Espinacas</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Pescados</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Tapa</th>
|
||||
<th>1/2 Ración</th>
|
||||
<th>Ración</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chipirón a la Plancha/Frito</td>
|
||||
<td>3.50€</td>
|
||||
<td>8.00€</td>
|
||||
<td>16.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Calamares Fritos</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Choco Frito</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Boquerones</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Acedias</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Puntillitas</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Adobo</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Brocheta de Gambas</td>
|
||||
<td>3.00€</td>
|
||||
<!-- Precio raro preguntar. -->
|
||||
<td>8.00€</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pescado Variado</td>
|
||||
<td>--</td>
|
||||
<td>7.00€</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pez Espada</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Merluza.</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dorada.</td>
|
||||
<td>--</td>
|
||||
<td>--</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Postres</h2>
|
||||
<ol>
|
||||
<li>Arroz con leche
|
||||
<b>2.50€</b></li>
|
||||
<li>Natillas
|
||||
<b>2.50€</b></li>
|
||||
<li>Flan de Huevo
|
||||
<b>2.50€</b></li>
|
||||
<li>Helados Nestle
|
||||
<b>3.00€</b></li>
|
||||
</ol>
|
||||
<p>Por último desde Burguillos.info os indicamos que el precio
|
||||
por el servicio de pan y picos por comensal es de 0.50€ y que
|
||||
tenéis la posibilidad de pedir un extra en salsas por
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
</attributes>
|
||||
<slug>cafe-bar-beluche</slug>
|
||||
<content>
|
||||
<h2 style="background: black; color: red;">Menú actualizado con nuevos precios y productos 2023-09-13</h2>
|
||||
<img width="50%" alt="Front door of 'Café-Bar Beluche'" style="border: solid 1px black;" src="/img/beluche.webp"/>
|
||||
|
||||
<h2>Información de contacto para preparación de pedidos y envío a domicilio.</h2>
|
||||
|
@ -22,119 +23,402 @@
|
|||
|
||||
<p>Su ubicación en calle Albahaca número 13 es inmejorable, ofreciendo terrazas a parte de mesas en el interior.</p>
|
||||
|
||||
<p>El teléfono de contacto es <a href="tel:+34691492054">691 492 054</a>, puedes usarlo para reservar, pedir comida a domicilio o pedir que te preparen platos para llevar.</p>
|
||||
<p>Los teléfonos de contacto son <a href="tel:+34694200713">694 200 713</a> y <a href="tel:+34691492054">691 492 054</a>, puedes usarlos para reservar, pedir comida a domicilio o pedir que te preparen platos para llevar.</p>
|
||||
|
||||
<p>Procedemos a transcribir la carta a continuación:</p>
|
||||
|
||||
<details>
|
||||
<summary><h3>Ensaladas</h3></summary>
|
||||
<ul>
|
||||
<li>Mixta -- Precio: <b>6.00€</b></li>
|
||||
<li>César -- Precio: <b>6.50€</b></li>
|
||||
<li>Trópical -- Precio: <b>N/A</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Revueltos</h3></summary>
|
||||
<ul>
|
||||
<li>Gula langostinos -- Precio: <b>7.50€</b></li>
|
||||
<li>Bacalao dorado -- Precio: <b>7.50€</b></li>
|
||||
<li>Morcilla de arroz -- Precio: <b>7.50€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Tapas frías.</h3></summary>
|
||||
<ul>
|
||||
<li>Ensaladilla rusa -- Tapa: <b>3.00€</b> Plato: <b>6.00€</b></li>
|
||||
<li>Aliños del día (Aliño Melva/Salpicón de marisco) -- Tapa: <b>3.00€</b> Plato: <b>6.00€</b></li>
|
||||
<li>Cóctel de mariscos -- Plato: <b>4.00€</b></li>
|
||||
<li>Ensaladilla de cangrejo -- Tapa: <b>3.50€</b> Plato: <b>7.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Para compartir</h3></summary>
|
||||
<ul>
|
||||
<li>Papas bravas -- Precio tapa: <b>3.50€</b> Precio plato: <b>6.00€</b></li>
|
||||
<li>Papas de mi prima -- Precio tapa: <b>3.50€</b> Precio plato: <b>6.00€</b></li>
|
||||
<li>Papas arrieras -- Precio tapa: <b>3.80€</b> Precio plato: <b>7.00€</b></li>
|
||||
<li>Bartolitos. (Langostinos con bacon) -- Precio tapa: <b>3.80€</b> Precio plato: <b>6.00€</b></li>
|
||||
<li>Queso rulo con bacon, nueces y miel de caña -- Precio tapa: <b>3.80€</b> Precio plato: <b>6.00€</b></li>
|
||||
<li>Morcilla crocanti -- Precio tapa: <b>3.50€</b> Precio plato: <b>6.00€</b></li>
|
||||
<li>Muss de pato -- Precio tapa: <b>4.00€</b> Precio plato: <b>8.00€</b></li>
|
||||
<li>Duo de rulo y muss de pato -- Precio plato: <b>7.00€</b></li>
|
||||
<li>Talegitas de queso -- Precio tapa: <b>3.50€</b> Precio plato: <b>6.00€</b></li>
|
||||
<li>Champiñones con alioli y jamón -- Precio tapa: <b>3.50€</b> Precio plato: <b>6.50€</b></li>
|
||||
<li>Fideos tostados -- Precio plato: <b>4.00€</b></li>
|
||||
<li>Variado de croquetas -- Precio plato: <b>11.00€</b></li>
|
||||
<li>Croquetas de secreto y miel -- Precio tapa: <b>4.00€</b> Precio plato: <b>8.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Rico rico</h3></summary>
|
||||
<ul>
|
||||
<li>Pollo kentucky -- Precio tapa: <b>3.50€</b> Precio plato: <b>7.00€</b></li>
|
||||
<li>Pollo mostaza -- Precio tapa: <b>4.50€</b> Precio plato: <b>8.50€</b></li>
|
||||
<li>Huevos rotos -- Precio plato: <b>5.00€</b></li>
|
||||
<li>Pan bao -- Precio tapa: <b>5.00€</b> Precio plato: <b>10.00€</b></li>
|
||||
<li>Carrillada ibérica -- Precio tapa: <b>3.50€ (Preguntar, en menú real 8.50€)</b> Precio plato: <b>7.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Arroces</h3></summary>
|
||||
<ul>
|
||||
<li>Timbal de arroz con chipirones y alioli -- Tapa: <b>4.00€</b> Plato: <b>7.50€</b></li>
|
||||
<li>Arroz negro -- Tapa: <b>4.50€</b> Plato: <b>8.50€</b></li>
|
||||
<li>Rissotto cuatro quesos -- Tapa: <b>4.50€</b> Plato: <b>8.50€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Pescados</h3></summary>
|
||||
<ul>
|
||||
<li>Gambas al ajillo -- Precio tapa: <b>4.50€</b> Precio plato: <b>8.00€</b></li>
|
||||
<li>Gambas a la plancha -- Precio tapa: <b>5.00€</b> Precio plato: <b>10.00€</b></li>
|
||||
<li>Flamenquín de melva -- Precio tapa: <b>3.50€</b> Precio plato: <b>7.00€</b></li>
|
||||
<li>Chipirones -- Precio tapa: <b>4.00€</b> Precio plato: <b>7.50€</b></li>
|
||||
<li>Choco -- Precio plato: <b>S/P</b></li>
|
||||
<li>Lubina -- Precio plato: <b>S/P</b></li>
|
||||
<li>Pez espada -- Precio plato: <b>12.00€</b></li>
|
||||
<li>Merluza confitada -- Precio plato: <b>8.00€</b></li>
|
||||
<li>Bacalao confitado -- Precio plato: <b>9.00€</b></li>
|
||||
<li>Montadito de gambas con alioli -- Precio plato: <b>3.20€</b></li>
|
||||
<li>Almejas con langostinos -- Precio tapa: <b>4.50€</b> Precio plato: <b>8.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Carnes</h3></summary>
|
||||
<ul>
|
||||
<li>Solomillo de pavo -- Precio plato: <b>9.00€</b></li>
|
||||
<li>Medallones de solomillo (Roque, whisky, mojo) -- Precio tapa: <b>3.50€</b> Precio plato: <b>6.50€</b></li>
|
||||
<li>Solomillo rulo de queso y miel de caña -- Precio tapa: <b>4.00€</b> Precio plato: <b>7.50€</b></li>
|
||||
<li>Solomillo ibérico -- Precio plato: <b>12.50€</b></li>
|
||||
<li>Abanico ibérico -- Precio plato: <b>12.50€</b></li>
|
||||
<li>Presa ibérica -- Precio plato: <b>14.00€</b></li>
|
||||
<li>Presa con mostaza -- Precio tapa: <b>4.00€</b> Precio plato: <b>8.00€</b></li>
|
||||
<li>Presa con setas y roquefort -- Precio tapa: <b>4.50€</b> Precio plato: <b>9.00€</b></li>
|
||||
<li>Chuletón de ternera -- Precio plato: <b>S/P</b></li>
|
||||
<li>Entrecot de ternera -- Precio plato: <b>S/P</b></li>
|
||||
<li>Chuletón de vaca vieja madurada -- Precio plato: <b>S/P</b></li>
|
||||
<li>Entrecot de vaca vieja -- Precio plato: <b>S/P</b></li>
|
||||
<li>Hamburguesa de buey -- Precio plato: <b>5.50€</b></li>
|
||||
<li>Mini hamburguesa -- Precio plato: <b>3.00€</b></li>
|
||||
<li>Montadito de pollo o lomo -- Precio plato: <b>3.00€</b></li>
|
||||
<li>Montadito de solomillo y queso viejo -- Precio plato: <b>3.80€</b></li>
|
||||
<li>Serranito -- Precio plato: <b>5.50€</b></li>
|
||||
<li>Mini serranito de pollo o lomo -- Precio plato: <b>4.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h3>Postres</h3></summary>
|
||||
<ul>
|
||||
<li>Gofres con nata y chocolate -- Precio: <b>3.80€</b></li>
|
||||
<li>Tortitas americanas -- Precio: <b>4.00€</b></li>
|
||||
<li>Tarta (Porción) -- Precio: <b>3.80€</b></li>
|
||||
<li>Coulant con helado de vainilla -- Precio: <b>4.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<h3>Ensaladas</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mixta</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>César</td>
|
||||
<td>6.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trópical</td>
|
||||
<td>6.50€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<h3>Revueltos</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gula langostinos</td>
|
||||
<td>7.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bacalao dorado</td>
|
||||
<td>7.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Morcilla de arroz</td>
|
||||
<td>7.50€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Tapas frías.</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Tapa</th>
|
||||
<th>Plato</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ensaladilla rusa</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aliños del día (Aliño Melva/Salpicón de marisco)</td>
|
||||
<td>3.00€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cóctel de mariscos</td>
|
||||
<td>---</td>
|
||||
<td>4.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ensaladilla de cangrejo</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Para compartir</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio tapa</th>
|
||||
<th>Precio plato</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Papas bravas</td>
|
||||
<td>3.50€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Papas de mi prima</td>
|
||||
<td>3.50€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Papas arrieras</td>
|
||||
<td>3.80€</td>
|
||||
<td>7.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bartolitos. (Langostinos con bacon)</td>
|
||||
<td>3.80€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Queso rulo con bacon, nueces y miel de caña</td>
|
||||
<td>3.80€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Morcilla crocanti</td>
|
||||
<td>3.50€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Muss de pato</td>
|
||||
<td>4.00€</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Duo de rulo y muss de pato</td>
|
||||
<td>---</td>
|
||||
<td>7.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Talegitas de queso</td>
|
||||
<td>3.50€</td>
|
||||
<td>6.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Champiñones con alioli y jamón</td>
|
||||
<td>3.50€</td>
|
||||
<td>6.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fideos tostados</td>
|
||||
<td>---</td>
|
||||
<td>4.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Variado de croquetas</td>
|
||||
<td>---</td>
|
||||
<td>11.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Croquetas de secreto y miel</td>
|
||||
<td>4.00€</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Rico rico</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio tapa</th>
|
||||
<th>Precio plato</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pollo kentucky</td>
|
||||
<td>---</td>
|
||||
<td>3.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pollo mostaza</td>
|
||||
<td>4.50€</td>
|
||||
<td>8.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Huevos rotos</td>
|
||||
<td>---</td>
|
||||
<td>5.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pan bao</td>
|
||||
<td>5.00€</td>
|
||||
<td>10.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Carrillada ibérica</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Arroces</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Nombre</td>
|
||||
<td>Tapa</td>
|
||||
<td>Plato</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Timbal de arroz con chipirones y alioli</td>
|
||||
<td>4.00€</td>
|
||||
<td>7.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Arroz negro</td>
|
||||
<td>4.50€</td>
|
||||
<td>8.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bissotio cuatro quesos</td>
|
||||
<td>4.50€</td>
|
||||
<td>8.50€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Pescados</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio tapa</th>
|
||||
<th>Precio plato</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gambas al ajillo</td>
|
||||
<td>4.50€</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Flamenquín de melva</td>
|
||||
<td>3.50€</td>
|
||||
<td>7.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chipirones</td>
|
||||
<td>4.00€</td>
|
||||
<td>7.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Choco</td>
|
||||
<td>---</td>
|
||||
<td>S/P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lubina</td>
|
||||
<td>---</td>
|
||||
<td>S/P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pez espada</td>
|
||||
<td>---</td>
|
||||
<td>12.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Merluza confitada</td>
|
||||
<td>---</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bacalao confitado</td>
|
||||
<td>---</td>
|
||||
<td>9.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Montadito de gambas con alioli</td>
|
||||
<td>---</td>
|
||||
<td>3.20€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Almejas con langostinos</td>
|
||||
<td>4.50€</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Carnes</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio tapa</th>
|
||||
<th>Precio plato</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solomillo de pavo</td>
|
||||
<td>---</td>
|
||||
<td>9.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Medallones de solomillo (Roque, whisky, mojo)</td>
|
||||
<td>3.50€</td>
|
||||
<td>6.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solomillo rulo de queso y miel de caña</td>
|
||||
<td>4.00€</td>
|
||||
<td>7.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solomillo ibérico</td>
|
||||
<td>---</td>
|
||||
<td>12.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Abanico ibérico</td>
|
||||
<td>---</td>
|
||||
<td>12.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Presa ibérica</td>
|
||||
<td>---</td>
|
||||
<td>14.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Presa con mostaza</td>
|
||||
<td>4.00€</td>
|
||||
<td>8.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Presa con setas y roquefort</td>
|
||||
<td>4.50€</td>
|
||||
<td>9.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chuletón de ternera</td>
|
||||
<td>---</td>
|
||||
<td>S/P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Entrecot de ternera</td>
|
||||
<td>---</td>
|
||||
<td>S/P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chuletón de vaca vieja madurada</td>
|
||||
<td>---</td>
|
||||
<td>S/P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Entrecot de vaca vieja</td>
|
||||
<td>---</td>
|
||||
<td>S/P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hamburguesa de buey</td>
|
||||
<td>---</td>
|
||||
<td>5.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mini hamburguesa</td>
|
||||
<td>---</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Montadito de pollo o lomo</td>
|
||||
<td>---</td>
|
||||
<td>3.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Montadito de solomillo y queso viejo</td>
|
||||
<td>---</td>
|
||||
<td>3.80</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Serranito</td>
|
||||
<td>---</td>
|
||||
<td>5.50€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mini de pollo o lomo</td>
|
||||
<td>---</td>
|
||||
<td>4.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Postres</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Precio</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gofres con nata y chocolate</td>
|
||||
<td>3.80€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tortitas americanas</td>
|
||||
<td>4.00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tarta (Porción)</td>
|
||||
<td>3.80€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Coulant con helado de vainilla</td>
|
||||
<td>4.00€</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>¿Quieres ver tu negocio localizado en Burguillos en este espacio? Contacta con <a href="mailto:contact@owlcode.tech">contact@owlcode.tech</a>.</p>
|
||||
</content>
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
</attributes>
|
||||
<slug>hamburgueseria-la-ermita</slug>
|
||||
<content>
|
||||
|
||||
<h2 style="background: black; color: red;">Menú actualizado con nuevos precios y productos 2024-01-18</h2>
|
||||
<img width="50%" alt="Front door of 'Hamburguesería la Ermita'" style="border: solid 1px black;" src="/img/hamburgueseria-la-ermita.webp"/>
|
||||
|
||||
<h2>Información de contacto para preparación de pedidos.</h2>
|
||||
|
@ -25,194 +23,174 @@
|
|||
|
||||
<p>Procedemos a listar la carta.</p>
|
||||
|
||||
<details>
|
||||
<summary><h3>Entrantes.</h3></summary>
|
||||
<h3>Entrantes.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Alitas <b>3€ tapa</b>.</li>
|
||||
<li>Chili Cheese Bite <b>3€ tapa</b>.</li>
|
||||
<li>Fingers de mozarella <b>3€ tapa</b>.</li>
|
||||
<li>Aros de cebolla <b>3€ tapa</b>.</li>
|
||||
<li>Nuggets <b>3€ tapa</b>.</li>
|
||||
<li>Crujientes de pollo <b>3€ tapa</b>.</li>
|
||||
<li>Lagrimitas <b>3€ tapa</b>.</li>
|
||||
<li>Croquetas de jamón / queso azul y cebolla caramelizada / pizza <b>3€ tapa</b>.</li>
|
||||
<li>Patatas alioli <b>3€ tapa 5.50€ plato</b>.</li>
|
||||
<li>Ensaladilla <b>3€ tapa 5.50€ plato</b>.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Alitas <b>3€ tapa</b>.</li>
|
||||
<li>Chili Cheese Bite <b>3€ tapa</b>.</li>
|
||||
<li>Fingers de mozarella <b>3€ tapa</b>.</li>
|
||||
<li>Aros de cebolla <b>3€ tapa</b>.</li>
|
||||
<li>Nuggets <b>3€ tapa</b>.</li>
|
||||
<li>Crujientes de pollo <b>3€ tapa</b>.</li>
|
||||
<li>Lagrimitas <b>3€ tapa</b>.</li>
|
||||
<li>Croquetas de jamón / queso azul y cebolla caramelizada / pizza <b>3€ tapa</b>.</li>
|
||||
<li>Patatas alioli <b>2.50€ tapa 4€ plato</b>.</li>
|
||||
<li>Ensaladilla <b>2.50€ tapa 4€ plato</b>.</li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Pizzas.</h3></summary>
|
||||
<h3>Pizzas.</h3>
|
||||
|
||||
<p>Todas las pizzas vienen con 2 ingredientes incluidos en el precio, por 0.60€ puedes añadir un ingrediente extra por 0.70€ o una salsa extra por 0.50€.</p>
|
||||
<p>Todas las pizzas vienen con 2 ingredientes incluidos en el precio, por 0.60€ puedes añadir un ingrediente extra por 0.60€ o una salsa extra por 0.30€.</p>
|
||||
|
||||
<p>Tienes la posibilidad de pedir una pizza mediana nutella por 5.50€ desde Burguillos.info suponemos que no se permiten
|
||||
otros ingredientes para evitar aberraciones gastronómicas.</p>
|
||||
<p>Tienes la posibilidad de pedir una pizza mediana nutella por 5.50€ desde Burguillos.info suponemos que no se permiten
|
||||
otros ingredientes para evitar aberraciones gastronómicas.</p>
|
||||
|
||||
<h4>Tamaño de pizza.</h4>
|
||||
<h4>Tamaño de pizza.</h4>
|
||||
|
||||
<ul>
|
||||
<li>Mediana <b>5.90€</b>.</li>
|
||||
<li>Grande <b>9.50€</b>.</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Mediana <b>5.50€</b>.</li>
|
||||
<li>Grande <b>9.00€</b>.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Ingredientes disponibles.</h4>
|
||||
<h4>Ingredientes disponibles.</h4>
|
||||
|
||||
<ul>
|
||||
<li>Jamón York</li>
|
||||
<li>Bacon</li>
|
||||
<li>Salchicha</li>
|
||||
<li>Pepperoni</li>
|
||||
<li>Roquefort</li>
|
||||
<li>Jamón</li>
|
||||
<li>Barbacoa</li>
|
||||
<li>Atún</li>
|
||||
<li>Cebolla</li>
|
||||
<li>Pimientos</li>
|
||||
<li>Gambas</li>
|
||||
<li>Huevo</li>
|
||||
<li>Aceitunas</li>
|
||||
<li>Pepinillos</li>
|
||||
<li>Champiñones</li>
|
||||
<li>Maíz</li>
|
||||
<li>Piña</li>
|
||||
<li>Anchoa</li>
|
||||
<li>Rulo de cabra</li>
|
||||
<li>Rúcula</li>
|
||||
<li>Tomate natural</li>
|
||||
<li>Carbonara</li>
|
||||
<li>Carne kebab</li>
|
||||
<li>Pollo asado y salsa kebab</li>
|
||||
<li>4 quesos</li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Jamón York</li>
|
||||
<li>Bacon</li>
|
||||
<li>Salchicha</li>
|
||||
<li>Pepperoni</li>
|
||||
<li>Roquefort</li>
|
||||
<li>Jamón</li>
|
||||
<li>Barbacoa</li>
|
||||
<li>Atún</li>
|
||||
<li>Cebolla</li>
|
||||
<li>Pimientos</li>
|
||||
<li>Gambas</li>
|
||||
<li>Huevo</li>
|
||||
<li>Aceitunas</li>
|
||||
<li>Pepinillos</li>
|
||||
<li>Champiñones</li>
|
||||
<li>Maíz</li>
|
||||
<li>Piña</li>
|
||||
<li>Anchoa</li>
|
||||
<li>Rulo de cabra</li>
|
||||
<li>Rúcula</li>
|
||||
<li>Tomate natural</li>
|
||||
<li>Carbonara</li>
|
||||
<li>Carne kebab</li>
|
||||
<li>Pollo asado y salsa kebab</li>
|
||||
<li>4 quesos</li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Bebidas.</h3></summary>
|
||||
<h3>Bebidas.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Refresco <b>1.50€</b></li>
|
||||
<li>Cerveza de barril <b>1.40€</b></li>
|
||||
<li>Tercio <b>1.50€</b></li>
|
||||
<li>Tinto <b>1.50€</b></li>
|
||||
<li>Radler <b>1.50€</b></li>
|
||||
<li>Cerveza sin alcohol botellín <b>1.30€</b></li>
|
||||
<li>Litro <b>3.00€</b></li>
|
||||
<li>Agua pequeña <b>1.00€</b></li>
|
||||
<li>Agua grande <b>1.50€</b></li>
|
||||
<li>Zumo <b>1.20€</b></li>
|
||||
<li>Café e infusión <b>1.20€</b></li>
|
||||
<li>Combinado <b>5.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Refresco <b>1.50€</b></li>
|
||||
<li>Cerveza de barril <b>1.30€</b></li>
|
||||
<li>Tercio <b>1.50€</b></li>
|
||||
<li>Tinto <b>1.50€</b></li>
|
||||
<li>Radler <b>1.50€</b></li>
|
||||
<li>Cerveza sin alcohol botellín <b>1.20€</b></li>
|
||||
<li>Litro <b>3.00€</b></li>
|
||||
<li>Agua pequeña <b>0.80€</b></li>
|
||||
<li>Agua grande <b>1.20€</b></li>
|
||||
<li>Zumo <b>1.00€</b></li>
|
||||
<li>Café e infusión <b>1.20€</b></li>
|
||||
<li>Combinado <b>4.50€</b></li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Helados.</h3></summary>
|
||||
<h3>Helados.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Sandy <b>2.30€</b></li>
|
||||
<li>Mix Sandy <b>3.30€</b></li>
|
||||
<li>Mini Mix Sandy <b>2.60€</b></li>
|
||||
<li>Batido pequeño <b>2.60€</b></li>
|
||||
<li>Batido grande <b>3.30€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Sandy <b>2.20€</b></li>
|
||||
<li>Mini Sandy <b>1.50€</b></li>
|
||||
<li>Mix Sandy <b>3.20€</b></li>
|
||||
<li>Mini Mix Sandy <b>2.50€</b></li>
|
||||
<li>Batido pequeño <b>2.50€</b></li>
|
||||
<li>Batido grande <b>3.20€</b></li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Bocadillos.</h3></summary>
|
||||
<h3>Bocadillos.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Tortilla <b>3.50€</b></li>
|
||||
<li>Cochinito <b>3.20€</b></li>
|
||||
<li>Lomo adobado <b>3.20€</b></li>
|
||||
<li>Bacon y queso <b>3.20€</b></li>
|
||||
<li>Atún y pimiento <b>3.20€</b></li>
|
||||
<li>Carne mechada y chimichurri <b>3.70€</b></li>
|
||||
<li>Filete de lomo, mayonesa y lechuga <b>3.90€</b></li>
|
||||
<li>Serranito de pollo o cerdo <b>4.90€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Tortilla <b>3.00€</b></li>
|
||||
<li>Cochinito <b>3.00€</b></li>
|
||||
<li>Lomo adobado <b>3.00€</b></li>
|
||||
<li>Bacon y queso <b>3.00€</b></li>
|
||||
<li>Atún y pimiento <b>3.00€</b></li>
|
||||
<li>Carne mechada y chimichurri <b>3.50€</b></li>
|
||||
<li>Filete de lomo, mayonesa y lechuga <b>3.50€</b></li>
|
||||
<li>Serranito de pollo o cerdo <b>4.50€</b></li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Montaditos.</h3></summary>
|
||||
<h3>Montaditos.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Gambas alioli <b>3.00€</b></li>
|
||||
<li>Melva con pimiento <b>3.00€</b></li>
|
||||
<li>Carne mechada con chimichurri <b>3.00€</b></li>
|
||||
<li>Solomillo (Whisky, roquefort o pimienta) <b>3.00€</b></li>
|
||||
<li>Pollo o lomo <b>3.00€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Gambas alioli <b>2.50€</b></li>
|
||||
<li>Melva con pimiento <b>2.50€</b></li>
|
||||
<li>Carne mechada con chimichurri <b>2.50€</b></li>
|
||||
<li>Solomillo (Whisky, roquefort o pimienta) <b>2.50€</b></li>
|
||||
<li>Pollo o lomo <b>2.50€</b></li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Perritos.</h3></summary>
|
||||
<h3>Perritos.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Simple (Salchicha + salsa) <b>2.20€</b></li>
|
||||
<li>Completo (Salchicha, cebolla frita, zanahoria, patatas paja y salsa) <b>2.70€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Simple (Salchicha + salsa) <b>2.00€</b></li>
|
||||
<li>Completo (Salchicha, cebolla frita, zanahoria, patatas paja y salsa) <b>2.50€</b></li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Carnes.</h3></summary>
|
||||
|
||||
<ul>
|
||||
<li>Pechuga y patatas <b>5.90€ plato</b>.</li>
|
||||
<li>Solomillo (Whisky, roquefort o pimienta) <b>3.00€ tapa 5.90€ plato</b>.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h3>Carnes.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Pechuga y patatas <b>5.00€ plato</b>.</li>
|
||||
<li>Solomillo (Whisky, roquefort o pimienta) <b>3.00€ tapa 5.00€ plato</b>.</li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Patatas gratinadas.</h3></summary>
|
||||
<h3>Patatas gratinadas.</h3>
|
||||
|
||||
<p>En formato pequeño cuestan 4.00€ y en formato grande 6.00€, a elegir entre las siguientes combinaciones.</p>
|
||||
<p>En formato pequeño cuestan 4.00€ y en formato grande 6.00€, a elegir entre las siguientes combinaciones.</p>
|
||||
|
||||
<ul>
|
||||
<li>Alioli + queso + bacon.</li>
|
||||
<li>Salsa cheedar + bacon + cebolla frita.</li>
|
||||
<li>Salsa kebab + carne kebab.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Alioli + queso + bacon.</li>
|
||||
<li>Salsa cheedar + bacon + cebolla frita.</li>
|
||||
<li>Salsa kebab + carne kebab.</li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Patatas normales.</h3></summary>
|
||||
<h3>Patatas normales.</h3>
|
||||
|
||||
<ul>
|
||||
<li>Pequeñas <b>1.00€</b></li>
|
||||
<li>Grandes <b>1.50€</b></li>
|
||||
<li>Gajo <b>grande 1.50€</b></li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Pequeñas <b>1.00€</b></li>
|
||||
<li>Grandes <b>1.50€</b></li>
|
||||
<li>Gajo <b>grande 1.50€</b></li>
|
||||
<li>Cris Criss - Cross <b>grande 2.00€</b></li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary><h3>Hamburguesas.</h3></summary>
|
||||
<h3>Hamburguesas.</h3>
|
||||
|
||||
<p>Puedes solicitar un extra en salsa por 0.50€.</p>
|
||||
<p>Puedes solicitar un extra en salsa por 0.50€.</p>
|
||||
|
||||
<ul>
|
||||
<li>Solo carne <b>1.80€</b>.</li>
|
||||
<li>Solo queso <b>2.30€</b>.</li>
|
||||
<li>BBQ (Carne, salsa barbacoa, queso y pepinillo) <b>2.90€</b>.</li>
|
||||
<li>Texas (Carne, salsa barbacoa, queso y aros de cebolla) <b>2.90€</b>.</li>
|
||||
<li>Cheedar simple (Carne, salsa cheedar, tomate, cebolla frita) <b>2.90€</b>.</li>
|
||||
<li>Salad (Carne, mayonesa, lechuga y tomate) <b>2.90€</b>.</li>
|
||||
<li>Porky (Carne cerdo, bacon, salsa bacon, queso, tomate, cebolla frita) + patatas <b>6.90€</b>.</li>
|
||||
<li>Roquefort (Carne mixta, salsa roquefort, lechuga, tomate, queso granapadano, bacon y cebolla frita) + patatas <b>6.90€</b>.</li>
|
||||
<li>Campera (Pollo campero, mayonesa, lechuga, tomate, queso y bacon) + patatas <b>6.90€</b>.</li>
|
||||
<li>Miel y mostaza (Carne mixta, rúcula, tomate, cebolla caramelizada, queso gouda, bacon, salsa miel y mostaza) + patatas <b>6.90€</b>.</li>
|
||||
<li>Boletus (Carne mixta, queso gouda, bacon, champiñones y salsa boletus) + patatas <b>6.90€</b>.</li>
|
||||
<li>La Ermita (Carne retinto, salsa barbacoa especial, tomate, cebolla frita, rulo de cabra y bacon) + patatas <b>7.90€</b>.</li>
|
||||
<li>Cabrales (Carne mixta, salsa cabrales, gouda, bacon, lechuga, tomate y queso) + patatas <b>6.90€</b>.</li>
|
||||
<li>Cheedar (Carne mixta o pollo empanado, salsa cheedar, cebolla frita, tomate, queso y bacon) + patatas <b>6.90€</b>.</li>
|
||||
<li>Completa (Carne mixta, mayonesa, lechuga, cebolla frita, tomate, pepinillo y cebolla) + patatas <b>6.90€</b>.</li>
|
||||
<li>Huevo (Carne mixta, mayonesa, lechuga, cebolla frita, tomate, queso, huevo y bacon) + patatas <b>6.90€</b>.</li>
|
||||
<li>Steak (Carne mixta, mayonesa, lechuga, tomate, queso, bacon y salsa bbq) + patatas <b>6.90€</b>.</li>
|
||||
<li>Cheese bacon (Carne mixta, queso, bacon, pepinillo, ketchup, mostaza y cebolla) + patatas <b>6.90€</b>.</li>
|
||||
<li>Romana (Carne mixta, salsa bbq, lechuga, tomate, queso y aros de cebolla) + patatas <b>6.90€</b>.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<ul>
|
||||
<li>Solo carne <b>1.50€</b>.</li>
|
||||
<li>Solo queso <b>2.00€</b>.</li>
|
||||
<li>BBQ (Carne, salsa barbacoa, queso y pepinillo) <b>2.50€</b>.</li>
|
||||
<li>Texas (Carne, salsa barbacoa, queso y aros de cebolla) <b>2.50€</b>.</li>
|
||||
<li>Cheedar simple (Carne, salsa cheedar, tomate, cebolla frita) <b>2.50€</b>.</li>
|
||||
<li>Salad (Carne, mayonesa, lechuga y tomate) <b>2.50€</b>.</li>
|
||||
<li>Porky (Carne cerdo, bacon, salsa bacon, queso, tomate, cebolla frita) + patatas <b>6.50€</b>.</li>
|
||||
<li>Roquefort (Carne mixta, salsa roquefort, lechuga, tomate, queso granapadano, bacon y cebolla frita) + patatas <b>6.50€</b>.</li>
|
||||
<li>Campera (Pollo campero, mayonesa, lechuga, tomate, queso y bacon) + patatas <b>6.50€</b>.</li>
|
||||
<li>Miel y mostaza (Carne mixta, rúcula, tomate, cebolla caramelizada, queso gouda, bacon, salsa miel y mostaza) + patatas <b>6.50€</b>.</li>
|
||||
<li>Boletus (Carne mixta, queso gouda, bacon, champiñones y salsa boletus) + patatas <b>6.50€</b>.</li>
|
||||
<li>La Ermita (Carne retinto, salsa barbacoa especial, tomate, cebolla frita, rulo de cabra y bacon) + patatas <b>7.50€</b>.</li>
|
||||
<li>Cabrales (Carne mixta, salsa cabrales, gouda, bacon, lechuga, tomate y queso) + patatas <b>6.50€</b>.</li>
|
||||
<li>Cheedar (Carne mixta o pollo empanado, salsa cheedar, cebolla frita, tomate, queso y bacon) + patatas <b>6.50€</b>.</li>
|
||||
<li>Completa (Carne mixta, mayonesa, lechuga, cebolla frita, tomate, pepinillo y cebolla) + patatas <b>6.50€</b>.</li>
|
||||
<li>Huevo (Carne mixta, mayonesa, lechuga, cebolla frita, tomate, queso, huevo y bacon) + patatas <b>6.50€</b>.</li>
|
||||
<li>Steak (Carne mixta, mayonesa, lechuga, tomate, queso, bacon y salsa bbq) + patatas <b>6.50€</b>.</li>
|
||||
<li>Cheese bacon (Carne mixta, queso, bacon, pepinillo, ketchup, mostaza y cebolla) + patatas <b>6.50€</b>.</li>
|
||||
<li>Romana (Carne mixta, salsa bbq, lechuga, tomate, queso y aros de cebolla) + patatas <b>6.50€</b>.</li>
|
||||
</ul>
|
||||
|
||||
<p>¿Quieres ver tu negocio localizado en Burguillos en este espacio? Contacta con <a href="mailto:contact@owlcode.tech">contact@owlcode.tech</a>.</p>
|
||||
</content>
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<post>
|
||||
<author>Burguillos.info</author>
|
||||
<date>2022-12-02T12:56+00:00</date>
|
||||
<title>Burguillos Dental - Dentista en Burguillos.</title>
|
||||
<ogdesc>Burguillos Dental - Dentista en Burguillos.</ogdesc>
|
||||
<category>dentistas</category>
|
||||
<slug>burguillos-dental</slug>
|
||||
<img src="/img/burguillos-dental.webp"/>
|
||||
<content>
|
||||
<img alt="" src="/img/burguillos-dental.webp"/>
|
||||
|
||||
<h2>Hazte tu aparato dental/ortodoncia invisible en Burguillos Dental.</h2>
|
||||
<p>Burguillos Dental, ubicado en la <a href="/posts/centro-medico-juan-manuel-perez-sanchez">Clínica Juan Manuel Pérez Sánchez</a> poseé un equipo de odontologos y dentistas altamente cualificados.</p>
|
||||
|
||||
<p>Ofrecen los siguientes servicios para el cuidado de tu boca:</p>
|
||||
<ul>
|
||||
<li>Odontologia general.</li>
|
||||
<li>Ortodoncia.</li>
|
||||
<li>Ortodoncia Invisible.</li>
|
||||
<li>Odontopediatria.</li>
|
||||
<li>Estetica Dental y Labios.</li>
|
||||
<li>Protesis y Aparatos Dentales, Implantes.</li>
|
||||
<li>Plan de atención infantil de la Junta de Andalucía.</li>
|
||||
<li>Radiografías Panorex-Teleradiografía.</li>
|
||||
</ul>
|
||||
|
||||
<p>Cita previa en <a href="tel:+34635061176">635061176</a> o <a href="mailto:policlinicaburguillos@gmail.com">policlinicaburguillos@gmail.com</a>.</p>
|
||||
|
||||
<p>Localizado en Calle la Fuente número 24.</p>
|
||||
</content>
|
||||
</post>
|
|
@ -1 +0,0 @@
|
|||
Subproject commit ae2bd63d8fbf93b8bb2a2fa9cc16405a16d0223d
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm -rf ./js-src/generated/
|
||||
mkdir ./js-src/generated/
|
||||
protoc --plugin="protoc-gen-ts=./node_modules/.bin/protoc-gen-ts" \
|
||||
--plugin="protoc-gen-js=./node_modules/.bin/protoc-gen-js" \
|
||||
--ts_opt=esModuleInterop=true \
|
||||
--js_out="import_style=commonjs,binary:./js-src/generated" \
|
||||
--ts_out="./js-src/generated" \
|
||||
--proto_path="proto" \
|
||||
$(find proto/ -name '*.proto')
|
|
@ -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.'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
export default class FightSelectorSlide {
|
||||
private callbacks: Record<string, Array<() => void>> = {}
|
||||
|
||||
public on(eventName: string, callback: () => void): void {
|
||||
if (this.callbacks[eventName] === undefined) {
|
||||
this.callbacks[eventName] = []
|
||||
}
|
||||
this.callbacks[eventName].push(callback)
|
||||
}
|
||||
|
||||
private runCallbacks(eventName: string) {
|
||||
const callbacks = this.callbacks[eventName];
|
||||
if (callbacks === undefined) {
|
||||
return
|
||||
}
|
||||
for (const callback of callbacks) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
private getSelectorSlide(): HTMLElement {
|
||||
const selectorSlide = document.querySelector('#fight-battle-selector-slide');
|
||||
if (!(selectorSlide instanceof HTMLElement)) {
|
||||
Conquer.fail('selectorSlide is not HTMLElement');
|
||||
}
|
||||
return selectorSlide;
|
||||
}
|
||||
public startHook(): void {
|
||||
this.createEventListeners();
|
||||
}
|
||||
|
||||
public getGlobalBattleButton(): HTMLElement {
|
||||
const globalBattleButton = this.getSelectorSlide().querySelector('button.fight-global-button');
|
||||
if (!(globalBattleButton instanceof HTMLElement)) {
|
||||
Conquer.fail('globalBattleButton is not HTMLElement');
|
||||
}
|
||||
return globalBattleButton;
|
||||
}
|
||||
|
||||
private createEventListeners(): void {
|
||||
const globalBattleButton = this.getGlobalBattleButton();
|
||||
globalBattleButton.addEventListener('click', () => {
|
||||
this.runCallbacks('global-battle');
|
||||
});
|
||||
}
|
||||
public show(): void {
|
||||
this.getSelectorSlide().classList.remove('conquer-display-none');
|
||||
}
|
||||
public hide(): void {
|
||||
this.getSelectorSlide().classList.add('conquer-display-none');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,584 @@
|
|||
import Map from "ol/Map"
|
||||
import MapEvent from "ol/MapEvent"
|
||||
import MapBrowserEvent from "ol/MapBrowserEvent"
|
||||
import View from "ol/View"
|
||||
import Projection from "ol/proj/Projection.js"
|
||||
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'
|
||||
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 Icon from 'ol/style/Icon'
|
||||
import Style from 'ol/style/Style'
|
||||
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'
|
||||
import NewNodeUI from '@burguillosinfo/conquer/interface/new-node'
|
||||
import NewTeamUI from '@burguillosinfo/conquer/interface/new-team'
|
||||
import WebSocket from '@burguillosinfo/conquer/websocket'
|
||||
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||
import FightSelectorSlide from '@burguillosinfo/conquer/fight-selector-slide';
|
||||
import SelectFightUI from '@burguillosinfo/conquer/interface/select-fight';
|
||||
import ConquerUserCurrentEnemy from '@burguillosinfo/conquer/user-current-enemy'
|
||||
|
||||
type StylesInterface = Record<string, Style>
|
||||
|
||||
export default class Conquer {
|
||||
private conquerContainer: HTMLDivElement
|
||||
private map: Map
|
||||
private enabledOnRotate = true
|
||||
private rotation = 0;
|
||||
private currentLongitude: number
|
||||
private intervalSendCoordinates: number | null = null;
|
||||
private currentLatitude: number
|
||||
private rotationOffset = 0
|
||||
private heading = 0
|
||||
private disableSetRotationOffset = false
|
||||
private currentPositionFeature: Feature | null
|
||||
private vectorLayer: VectorLayer<VectorSource> | null = null
|
||||
private alpha = 0
|
||||
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> = {}
|
||||
private coordinate_1 = 0;
|
||||
private coordinate_2 = 0;
|
||||
private fightSelectorSlide: FightSelectorSlide;
|
||||
private loggedIn = false;
|
||||
|
||||
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 refreshFightSlide(): void {
|
||||
if (this.loggedIn && (this.getState() & MapState.NORMAL) !== 0) {
|
||||
this.fightSelectorSlide.show();
|
||||
}
|
||||
if (!this.loggedIn) {
|
||||
this.fightSelectorSlide.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private refreshState(): void {
|
||||
this.refreshFightSlide();
|
||||
this.createNodeObject.refreshState()
|
||||
return
|
||||
}
|
||||
|
||||
static start() {
|
||||
const conquerContainer = document.querySelector(".conquer-container")
|
||||
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) {
|
||||
if (this.firstSetCenter || !(this.state & MapState.FREE_MOVE)) {
|
||||
this.coordinate_1 = lon;
|
||||
this.coordinate_2 = lat;
|
||||
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
|
||||
}
|
||||
}
|
||||
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 newNodeUI = new NewNodeUI(coordinates)
|
||||
const oldState = this.getState();
|
||||
newNodeUI.on('close', () => {
|
||||
this.interfaceManager.remove(newNodeUI)
|
||||
this.setState(oldState);
|
||||
})
|
||||
this.interfaceManager.push(newNodeUI)
|
||||
this.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
|
||||
}
|
||||
|
||||
private isStateFillingFormCreateNode(): boolean {
|
||||
return !!(this.getState() & MapState.FILLING_FORM_CREATE_NODE)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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> {
|
||||
if (!(this.state & MapState.NORMAL)) {
|
||||
return
|
||||
}
|
||||
const selfPlayerUI = new SelfPlayerUI(!!(this.getState() & (MapState.FREE_MOVE)))
|
||||
selfPlayerUI.on('close', () => {
|
||||
this.interfaceManager.remove(selfPlayerUI)
|
||||
})
|
||||
selfPlayerUI.on('enable-explorer-mode', () => {
|
||||
this.addState(MapState.FREE_MOVE);
|
||||
});
|
||||
selfPlayerUI.on('disable-explorer-mode', () => {
|
||||
this.removeState(MapState.FREE_MOVE);
|
||||
});
|
||||
selfPlayerUI.on('createNodeStart', () => {
|
||||
this.addState(MapState.CREATE_NODE)
|
||||
this.removeState(MapState.NORMAL)
|
||||
})
|
||||
selfPlayerUI.on('open-create-team', () => {
|
||||
this.onOpenCreateTeam();
|
||||
});
|
||||
this.interfaceManager.push(selfPlayerUI)
|
||||
this.selfPlayerUI = selfPlayerUI
|
||||
}
|
||||
|
||||
private onOpenCreateTeam(): void {
|
||||
const newTeamUI = new NewTeamUI();
|
||||
newTeamUI.on('close', () => {
|
||||
this.interfaceManager.remove(newTeamUI);
|
||||
});
|
||||
this.interfaceManager.push(newTeamUI);
|
||||
}
|
||||
|
||||
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);
|
||||
const candidateNode = this.getServerNodes()[feature.getProperties().type];
|
||||
if (candidateNode !== undefined) {
|
||||
candidateNode.click(this.interfaceManager);
|
||||
return;
|
||||
}
|
||||
if (feature === this.currentPositionFeature) {
|
||||
this.onClickSelf()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
async onLoginSuccess(): Promise<void> {
|
||||
this.loggedIn = true;
|
||||
this.refreshFightSlide();
|
||||
this.clearIntervalSendCoordinates();
|
||||
this.createIntervalSendCoordinates();
|
||||
this.clearIntervalPollNearbyNodes();
|
||||
this.createIntervalPollNearbyNodes();
|
||||
}
|
||||
|
||||
private intervalPollNearbyNodes: number | null = null;
|
||||
|
||||
private clearIntervalPollNearbyNodes(): void {
|
||||
if (this.intervalPollNearbyNodes !== null) {
|
||||
window.clearInterval(this.intervalPollNearbyNodes)
|
||||
this.intervalPollNearbyNodes = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async getNearbyNodes(): Promise<void> {
|
||||
const urlNodes = new URL('/conquer/node/near', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(urlNodes);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error parseando json: ' + responseBody);
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
if (response.status !== 200) {
|
||||
console.error(responseBody.error);
|
||||
return;
|
||||
}
|
||||
const serverNodes: Record<string, MapNode> = {};
|
||||
const nodes = JsonSerializer.deserialize(responseBody, MapNode);
|
||||
if (!(nodes instanceof Array)) {
|
||||
console.error('Received null instead of node list.');
|
||||
return;
|
||||
}
|
||||
for (const node of nodes) {
|
||||
if (!(node instanceof MapNode)) {
|
||||
console.error('Received node is not a MapNode.');
|
||||
continue;
|
||||
}
|
||||
node.on('update-nodes', async () => {
|
||||
await this.sendCoordinatesToServer();
|
||||
this.getNearbyNodes();
|
||||
});
|
||||
serverNodes[node.getId()] = node;
|
||||
}
|
||||
this.serverNodes = serverNodes;
|
||||
this.refreshLayers();
|
||||
}
|
||||
|
||||
private createIntervalPollNearbyNodes(): void {
|
||||
this.getNearbyNodes();
|
||||
this.intervalPollNearbyNodes = window.setInterval(() => {
|
||||
this.getNearbyNodes();
|
||||
}, 40000)
|
||||
}
|
||||
|
||||
private createIntervalSendCoordinates(): void {
|
||||
this.intervalSendCoordinates = window.setInterval(() => {
|
||||
this.sendCoordinatesToServer();
|
||||
}, 40000);
|
||||
}
|
||||
|
||||
private async sendCoordinatesToServer(): Promise<void> {
|
||||
const urlLog = new URL('/conquer/user/coordinates', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(urlLog, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify([
|
||||
this.coordinate_1,
|
||||
this.coordinate_2,
|
||||
])});
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return;
|
||||
}
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = await res.json();
|
||||
} catch(error) {
|
||||
console.error('Error parseando json: ' + responseBody);
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
if (res.status !== 200) {
|
||||
console.error(responseBody.error);
|
||||
}
|
||||
}
|
||||
|
||||
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.on('logout', () => {
|
||||
this.onLogout();
|
||||
});
|
||||
conquerLogin.start()
|
||||
this.conquerLogin = conquerLogin
|
||||
this.fightSelectorSlide = new FightSelectorSlide();
|
||||
this.fightSelectorSlide.on('global-battle', () => {
|
||||
this.startGlobalBattleSelector();
|
||||
});
|
||||
this.fightSelectorSlide.startHook();
|
||||
}
|
||||
|
||||
private async startGlobalBattleSelector(): Promise<void> {
|
||||
const enemies = await ConquerUserCurrentEnemy.getGlobalEnemies();
|
||||
if (enemies !== null) {
|
||||
const selectFightUI = new SelectFightUI(enemies);
|
||||
selectFightUI.on('close', () => {
|
||||
this.interfaceManager.remove(selectFightUI);
|
||||
});
|
||||
this.interfaceManager.push(selectFightUI);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private onLogout(): void {
|
||||
this.loggedIn = false;
|
||||
this.refreshFightSlide();
|
||||
this.clearIntervalSendCoordinates();
|
||||
this.clearIntervalPollNearbyNodes();
|
||||
}
|
||||
|
||||
private clearIntervalSendCoordinates(): void {
|
||||
if (this.intervalSendCoordinates !== null) {
|
||||
window.clearInterval(this.intervalSendCoordinates);
|
||||
this.intervalSendCoordinates = null;
|
||||
}
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.runPreStartState()
|
||||
this.setState(MapState.NORMAL | MapState.FREE_ROTATION)
|
||||
const conquerContainer = this.conquerContainer
|
||||
//layer.on('prerender', (evt) => {
|
||||
// // return
|
||||
// if (evt.context) {
|
||||
// const context = evt.context as CanvasRenderingContext2D
|
||||
// context.filter = 'grayscale(80%) invert(100%) '
|
||||
// context.globalCompositeOperation = 'source-over'
|
||||
// }
|
||||
//})
|
||||
|
||||
//layer.on('postrender', (evt) => {
|
||||
// if (evt.context) {
|
||||
// const context = evt.context as CanvasRenderingContext2D
|
||||
// context.filter = 'none'
|
||||
// }
|
||||
//})
|
||||
olProj.useGeographic()
|
||||
const osm = new OSM()
|
||||
osm.setUrls([`${window.location.protocol}//${window.location.hostname}:${window.location.port}/conquer/tile/{z}/{x}/{y}.png`])
|
||||
this.map = new Map({
|
||||
target: conquerContainer,
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: osm
|
||||
})
|
||||
],
|
||||
view: new View({
|
||||
zoom: 19,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
|
||||
this.addCurrentLocationMarkerToMap(this.currentLatitude, this.currentLongitude)
|
||||
this.refreshLayers()
|
||||
}
|
||||
|
||||
private async refreshLayers(): Promise<void> {
|
||||
if (this.currentPositionFeature === null) {
|
||||
return
|
||||
}
|
||||
const user = await ConquerUser.getSelfUser()
|
||||
let color = 'white';
|
||||
if (user !== null) {
|
||||
const team = await user.getTeam();
|
||||
if (team !== null) {
|
||||
color = team.getColor();
|
||||
}
|
||||
}
|
||||
const styles: StylesInterface = {
|
||||
currentPositionFeature: new Style({
|
||||
image: new Icon({
|
||||
crossOrigin: 'anonymous',
|
||||
src: '/img/arrow-player.svg',
|
||||
color: color,
|
||||
scale: 0.2,
|
||||
rotation: this.rotation,
|
||||
rotateWithView: true,
|
||||
}),
|
||||
zIndex: 4,
|
||||
})
|
||||
};
|
||||
const features = [];
|
||||
features.push(this.currentPositionFeature);
|
||||
for (const key in this.getServerNodes()) {
|
||||
styles[key] = await this.getServerNodes()[key].getStyle()
|
||||
features.push(this.getServerNodes()[key].getFeature())
|
||||
}
|
||||
const vectorLayer = new VectorLayer<VectorSource>({
|
||||
source: new VectorSource({
|
||||
features: features
|
||||
}),
|
||||
})
|
||||
if (this.vectorLayer !== null) {
|
||||
this.map.removeLayer(this.vectorLayer)
|
||||
this.vectorLayer = null;
|
||||
}
|
||||
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 {
|
||||
window.setInterval(() => {
|
||||
this.disableSetRotationOffset = false
|
||||
}, 10000)
|
||||
this.currentPositionFeature = null
|
||||
window.setTimeout(() => {
|
||||
window.setInterval(() => {
|
||||
navigator.geolocation.getCurrentPosition((location) => {
|
||||
this.processLocation(location)
|
||||
}, () => {
|
||||
return
|
||||
}, {
|
||||
enableHighAccuracy: true,
|
||||
})
|
||||
}, 3000)
|
||||
}, 1000)
|
||||
// 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()
|
||||
navigator.geolocation.watchPosition((location) => {
|
||||
this.processLocation(location)
|
||||
}, (err) => {
|
||||
return
|
||||
}, {
|
||||
enableHighAccuracy: true,
|
||||
})
|
||||
}
|
||||
realCoordinatesToOl(lat: number, lon: number): number[] {
|
||||
return olProj.transform(
|
||||
[lon, lat],
|
||||
new Projection({ code: "WGS84" }),
|
||||
new Projection({ code: "EPSG:900913" }),
|
||||
)
|
||||
}
|
||||
compassHeading(alpha:number, beta:number, gamma:number): number {
|
||||
const alphaRad = alpha * (Math.PI / 180)
|
||||
return alphaRad
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
onRotate(alpha: number, beta: number, gamma: number) {
|
||||
if (this.enabledOnRotate) {
|
||||
this.alpha = alpha
|
||||
this.beta = beta
|
||||
this.gamma = gamma
|
||||
this.enabledOnRotate = false
|
||||
this.rotation = -(this.compassHeading(alpha, beta, gamma) - this.rotationOffset);
|
||||
if (this.currentPositionFeature !== null) {
|
||||
this.currentPositionFeature.changed();
|
||||
}
|
||||
if (this.firstSetRotation || !(this.state & MapState.FREE_ROTATION)) {
|
||||
this.map.getView().setRotation((this.compassHeading(alpha, beta, gamma) - this.rotationOffset))
|
||||
this.firstSetRotation = false
|
||||
}
|
||||
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.enabledOnRotate = true
|
||||
}, 10)
|
||||
}
|
||||
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
|
||||
}
|
||||
constructor(conquerContainer: HTMLDivElement) {
|
||||
this.conquerContainer = conquerContainer
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
import ConquerInterface from '@burguillosinfo/conquer/interface'
|
||||
|
||||
export default class ConquerInterfaceManager {
|
||||
private interfaces: ConquerInterface[] = []
|
||||
|
||||
public push(conquerInterface: ConquerInterface) {
|
||||
const nodesForInterface = conquerInterface.getNodes()
|
||||
for (const nodeInInterface of nodesForInterface) {
|
||||
nodeInInterface.id = ""
|
||||
document.body.appendChild(nodeInInterface)
|
||||
}
|
||||
this.interfaces.push(conquerInterface)
|
||||
conquerInterface.run()
|
||||
let startInterface = this.interfaces.length - 2;
|
||||
if (startInterface < 0) {
|
||||
startInterface = 0
|
||||
}
|
||||
this.recalculateAllZIndexes(startInterface)
|
||||
}
|
||||
|
||||
public remove(conquerInterface: ConquerInterface) {
|
||||
for (let i = this.interfaces.length - 1; i >= 0; i--) {
|
||||
if (conquerInterface !== this.interfaces[i]) {
|
||||
continue
|
||||
}
|
||||
this.interfaces.splice(i, 1)
|
||||
for (const nodeToDelete of conquerInterface.getNodes()) {
|
||||
document.body.removeChild(nodeToDelete)
|
||||
}
|
||||
conquerInterface.prune()
|
||||
this.recalculateAllZIndexes()
|
||||
}
|
||||
}
|
||||
|
||||
private recalculateAllZIndexes(start = 0) : void {
|
||||
let currentZindex = 5;
|
||||
if (start < 0) {
|
||||
Conquer.fail('ConquerInterfaceManager.recalculateAllZIndexes must not be passed negative values.')
|
||||
}
|
||||
if (start > 0) {
|
||||
const lastInterface = this.interfaces[start-1];
|
||||
if (lastInterface === undefined) {
|
||||
Conquer.fail('Last interface should not be null, dying...')
|
||||
}
|
||||
const lastInterfaceNodes = lastInterface.getNodes()
|
||||
const lastInterfaceLastNode = lastInterfaceNodes[lastInterfaceNodes.length-1]
|
||||
if (lastInterfaceLastNode === undefined) {
|
||||
Conquer.fail('Last interface last node should not be null, dying...')
|
||||
}
|
||||
currentZindex = parseInt(lastInterfaceLastNode.style.zIndex)
|
||||
}
|
||||
for (let i = start; i < this.interfaces.length; i++) {
|
||||
const conquerInterface = this.interfaces[i]
|
||||
for (const node of conquerInterface.getNodes()) {
|
||||
node.style.zIndex = currentZindex + ''
|
||||
currentZindex++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
export default abstract class ConquerInterface {
|
||||
private alreadyGenerated = false
|
||||
private nodes: HTMLElement[]
|
||||
private callbacks: Record<string, Array<() => void>> = {}
|
||||
public getNodes(): HTMLElement[] {
|
||||
if (!this.alreadyGenerated) {
|
||||
this.nodes = this.generateNodes()
|
||||
this.alreadyGenerated = true
|
||||
}
|
||||
return this.nodes
|
||||
}
|
||||
protected abstract generateNodes(): HTMLElement[]
|
||||
|
||||
public run(): void {
|
||||
return
|
||||
}
|
||||
|
||||
public prune(): void {
|
||||
this.callbacks = {};
|
||||
return
|
||||
}
|
||||
|
||||
protected getNodeFromTemplateId(id: string): HTMLElement {
|
||||
let template = document.getElementById(id)
|
||||
if (template === null) {
|
||||
Conquer.fail(`Unable to find template id ${id}.`)
|
||||
}
|
||||
const finalNode = template.cloneNode(true)
|
||||
if (!(finalNode instanceof HTMLElement)) {
|
||||
Conquer.fail('The node is not an Element.')
|
||||
}
|
||||
finalNode.classList.remove('conquer-display-none')
|
||||
return finalNode
|
||||
}
|
||||
|
||||
public on(eventName: string, callback: () => void): void {
|
||||
if (this.callbacks[eventName] === undefined) {
|
||||
this.callbacks[eventName] = []
|
||||
}
|
||||
this.callbacks[eventName].push(callback)
|
||||
}
|
||||
|
||||
protected runCallbacks(eventName: string) {
|
||||
const callbacks = this.callbacks[eventName];
|
||||
if (callbacks === undefined) {
|
||||
return
|
||||
}
|
||||
for (const callback of callbacks) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
import ConquerInterface from '@burguillosinfo/conquer/interface'
|
||||
|
||||
export default abstract class AbstractTopBarInterface extends ConquerInterface {
|
||||
constructor() {
|
||||
super()
|
||||
const exitButton = this.getExitButton()
|
||||
exitButton.addEventListener('click', () => {
|
||||
this.runCallbacks('close')
|
||||
})
|
||||
}
|
||||
protected generateNodes(): HTMLElement[] {
|
||||
const newNode = this.getNodeFromTemplateId('conquer-interface-with-top-bar-template')
|
||||
return [newNode]
|
||||
}
|
||||
protected getMainNode(): HTMLElement {
|
||||
return this.getNodes()[0]
|
||||
}
|
||||
protected getExitButton(): HTMLElement {
|
||||
const maybeExitButton = this.getMainNode().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')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
import ConquerLogin from '@burguillosinfo/conquer/login'
|
||||
import ConquerInterface from '@burguillosinfo/conquer/interface'
|
||||
|
||||
export default class LoginUI extends ConquerInterface {
|
||||
private conquerLogin: ConquerLogin
|
||||
|
||||
private conquerLoginGoToRegister: HTMLAnchorElement
|
||||
private conquerLoginError: HTMLParagraphElement
|
||||
private conquerLoginSuccess: HTMLParagraphElement
|
||||
private conquerLoginUsername: HTMLInputElement
|
||||
private conquerLoginPassword: HTMLInputElement
|
||||
private conquerLoginSubmit: HTMLButtonElement
|
||||
|
||||
private conquerRegisterGoToLogin: HTMLAnchorElement
|
||||
private conquerRegisterUsername: HTMLInputElement
|
||||
private conquerRegisterPassword: HTMLInputElement
|
||||
private conquerRegisterRepeatPassword: HTMLInputElement
|
||||
private conquerRegisterSubmit: HTMLButtonElement
|
||||
private conquerRegisterError: HTMLParagraphElement
|
||||
|
||||
constructor(conquerLogin: ConquerLogin) {
|
||||
super()
|
||||
this.conquerLogin = conquerLogin
|
||||
}
|
||||
|
||||
public run() {
|
||||
this.conquerLogin.on('login', () => {
|
||||
this.runCallbacks('close');
|
||||
});
|
||||
this.storeRegisterElements()
|
||||
this.storeLoginElements()
|
||||
}
|
||||
|
||||
private getLoginDiv(): HTMLDivElement {
|
||||
const element = this.getNodes()[1];
|
||||
if (element === undefined || !(element instanceof HTMLDivElement)) {
|
||||
Conquer.fail('Login is not a div.')
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
private getOverlayDiv(): HTMLDivElement {
|
||||
const element = this.getNodes()[0]
|
||||
if (element === undefined || !(element instanceof HTMLDivElement)) {
|
||||
Conquer.fail('Overlay transparent is not a div.')
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
private getRegisterDiv(): HTMLDivElement {
|
||||
const element = this.getNodes()[2]
|
||||
if (element === undefined || !(element instanceof HTMLDivElement)) {
|
||||
Conquer.fail('Register is not a div.')
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
public generateNodes(): HTMLElement[] {
|
||||
const resultArray = []
|
||||
const overlay = this.getNodeFromTemplateId('conquer-overlay-transparent-template')
|
||||
overlay.classList.remove('conquer-display-none')
|
||||
resultArray.push(overlay)
|
||||
const login = this.getNodeFromTemplateId('conquer-login-template')
|
||||
login.classList.remove('conquer-display-none')
|
||||
if (!(login instanceof HTMLDivElement)) {
|
||||
Conquer.fail('Login is required to be a Div.')
|
||||
}
|
||||
resultArray.push(login)
|
||||
const register = this.getNodeFromTemplateId('conquer-register-template')
|
||||
resultArray.push(register)
|
||||
return resultArray
|
||||
}
|
||||
private async storeRegisterElements() {
|
||||
const registerElement = this.getRegisterDiv()
|
||||
const conquerRegisterGoToLogin = registerElement.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()
|
||||
})
|
||||
const conquerRegisterUsername = registerElement.querySelector('.conquer-register-username')
|
||||
if (conquerRegisterUsername === null || !(conquerRegisterUsername instanceof HTMLInputElement)) {
|
||||
Conquer.fail('No username field in conquer register.')
|
||||
}
|
||||
this.conquerRegisterUsername = conquerRegisterUsername
|
||||
const conquerRegisterPassword = registerElement.querySelector('.conquer-register-password')
|
||||
if (conquerRegisterPassword === null || !(conquerRegisterPassword instanceof HTMLInputElement)) {
|
||||
Conquer.fail('No password field in conquer register.')
|
||||
}
|
||||
this.conquerRegisterPassword = conquerRegisterPassword
|
||||
const conquerRegisterRepeatPassword = registerElement.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 = registerElement.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()
|
||||
const username = this.conquerRegisterUsername.value
|
||||
const password = this.conquerRegisterPassword.value
|
||||
const repeatPassword = this.conquerRegisterRepeatPassword.value
|
||||
this.conquerLogin.onRegisterRequest(this, username, password, repeatPassword)
|
||||
})
|
||||
const conquerRegisterError = registerElement.querySelector('.conquer-register-error')
|
||||
if (conquerRegisterError === null || !(conquerRegisterError instanceof HTMLParagraphElement)) {
|
||||
Conquer.fail('Unable to find the conquer error element.')
|
||||
}
|
||||
this.conquerRegisterError = conquerRegisterError
|
||||
|
||||
}
|
||||
private storeLoginElements() {
|
||||
const loginElement = this.getLoginDiv()
|
||||
const conquerLoginGoToRegister = loginElement.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()
|
||||
})
|
||||
const conquerLoginError = loginElement.querySelector('.conquer-login-error')
|
||||
if (conquerLoginError === null || !(conquerLoginError instanceof HTMLParagraphElement)) {
|
||||
Conquer.fail('Unable to find conquer login error.')
|
||||
}
|
||||
this.conquerLoginError = conquerLoginError
|
||||
const conquerLoginSuccess = loginElement.querySelector('.conquer-login-success')
|
||||
if (conquerLoginSuccess === null || !(conquerLoginSuccess instanceof HTMLParagraphElement)) {
|
||||
Conquer.fail('Unable to find conquer login success.')
|
||||
}
|
||||
this.conquerLoginSuccess = conquerLoginSuccess
|
||||
const conquerLoginUsername = loginElement.querySelector('.conquer-login-username')
|
||||
if (conquerLoginUsername === null || !(conquerLoginUsername instanceof HTMLInputElement)) {
|
||||
Conquer.fail('Unable to find conquer login username field.')
|
||||
}
|
||||
this.conquerLoginUsername = conquerLoginUsername
|
||||
const conquerLoginPassword = loginElement.querySelector('.conquer-login-password')
|
||||
if (conquerLoginPassword === null || !(conquerLoginPassword instanceof HTMLInputElement)) {
|
||||
Conquer.fail('Unable to find conquer login password field.')
|
||||
}
|
||||
this.conquerLoginPassword = conquerLoginPassword
|
||||
const conquerLoginSubmit = loginElement.querySelector('.conquer-login-submit')
|
||||
if (conquerLoginSubmit === null || !(conquerLoginSubmit instanceof HTMLButtonElement)) {
|
||||
Conquer.fail('Unable to find the submit button for the login.')
|
||||
}
|
||||
this.conquerLoginSubmit = conquerLoginSubmit
|
||||
this.conquerLoginSubmit.addEventListener('click', (event: Event) => {
|
||||
event.preventDefault()
|
||||
const username = this.conquerLoginUsername.value
|
||||
const password = this.conquerLoginPassword.value
|
||||
this.conquerLogin.onLoginRequested(this, username, password)
|
||||
})
|
||||
}
|
||||
|
||||
private async goToRegister(): Promise<void> {
|
||||
await this.removeLoginRegisterCombo()
|
||||
const registerElement = this.getRegisterDiv()
|
||||
registerElement.classList.remove('conquer-display-none')
|
||||
}
|
||||
|
||||
public async removeLoginRegisterCombo(): Promise<void> {
|
||||
const registerElement = this.getRegisterDiv()
|
||||
const overlayElement = this.getOverlayDiv()
|
||||
overlayElement.classList.add('conquer-display-none')
|
||||
const loginElement = this.getLoginDiv()
|
||||
loginElement.classList.add('conquer-display-none')
|
||||
registerElement.classList.add('conquer-display-none')
|
||||
}
|
||||
|
||||
public async addNewLoginSuccessText(message: string): Promise<void> {
|
||||
this.unsetLoginAndRegisterErrors()
|
||||
this.conquerLoginSuccess.innerText = message
|
||||
this.conquerLoginSuccess.classList.remove('conquer-display-none')
|
||||
|
||||
}
|
||||
public async addNewLoginError(error: string): Promise<void> {
|
||||
this.unsetLoginAndRegisterErrors()
|
||||
this.conquerLoginSuccess.classList.add('conquer-display-none')
|
||||
this.conquerLoginError.innerText = error
|
||||
this.conquerLoginError.classList.remove('conquer-display-none')
|
||||
}
|
||||
public async addNewRegisterError(error: string): Promise<void> {
|
||||
this.unsetLoginAndRegisterErrors()
|
||||
this.conquerLoginSuccess.classList.add('conquer-display-none')
|
||||
this.conquerRegisterError.innerText = error
|
||||
this.conquerRegisterError.classList.remove('conquer-display-none')
|
||||
}
|
||||
public async unsetLoginAndRegisterErrors() {
|
||||
this.conquerRegisterError.classList.add('conquer-display-none')
|
||||
this.conquerLoginError.classList.add('conquer-display-none')
|
||||
}
|
||||
|
||||
public async goToLogin(): Promise<void> {
|
||||
await this.removeLoginRegisterCombo()
|
||||
const loginElement = this.getLoginDiv()
|
||||
loginElement.classList.remove('conquer-display-none')
|
||||
}
|
||||
|
||||
public async addNewLoginRegisterError(message: string): Promise<void> {
|
||||
this.addNewRegisterError(message)
|
||||
this.addNewLoginError(message)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||
import Conquer from '@burguillosinfo/conquer'
|
||||
|
||||
export default class NewNodeUI extends AbstractTopBarInterface {
|
||||
private coordinates: number[];
|
||||
public getSubmitButton(): HTMLElement {
|
||||
const submitButton = this.getMainNode().querySelector('button.new-node-form-submit')
|
||||
if (submitButton === null || !(submitButton instanceof HTMLElement)) {
|
||||
Conquer.fail('SubmitButton is null');
|
||||
}
|
||||
return submitButton;
|
||||
}
|
||||
public getErrorElement(): HTMLElement {
|
||||
const errorElement = this.getMainNode().querySelector('p.conquer-error');
|
||||
if (errorElement === null || !(errorElement instanceof HTMLElement)) {
|
||||
Conquer.fail('No error element set');
|
||||
}
|
||||
return errorElement;
|
||||
}
|
||||
public getSelectNodeType(): HTMLSelectElement {
|
||||
const selectElement = this.getMainNode().querySelector('select.conquer-node-type');
|
||||
if (selectElement === null || !(selectElement instanceof HTMLSelectElement)) {
|
||||
Conquer.fail('SelectElementNodeType is null');
|
||||
}
|
||||
return selectElement
|
||||
}
|
||||
public getInputNodeName(): HTMLInputElement {
|
||||
const nodeName = this.getMainNode().querySelector('input.conquer-node-name')
|
||||
if (nodeName === null || !(nodeName instanceof HTMLInputElement)) {
|
||||
Conquer.fail('NodeName is null');
|
||||
}
|
||||
return nodeName
|
||||
}
|
||||
public getTextAreaNodeDescription(): HTMLTextAreaElement {
|
||||
const nodeDescription = this.getMainNode().querySelector('textarea.conquer-node-description')
|
||||
if (nodeDescription === null || !(nodeDescription instanceof HTMLTextAreaElement)) {
|
||||
Conquer.fail('NodeDescription is null');
|
||||
}
|
||||
return nodeDescription
|
||||
}
|
||||
constructor(coordinates: number[]) {
|
||||
super()
|
||||
this.coordinates = coordinates
|
||||
}
|
||||
public run() {
|
||||
const mainNode = this.getMainNode()
|
||||
const form = this.getNodeFromTemplateId('conquer-new-node-form-creation-template')
|
||||
mainNode.append(form)
|
||||
this.getSubmitButton().addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
this.onSubmit();
|
||||
});
|
||||
form.classList.remove('conquer-display-none')
|
||||
mainNode.classList.remove('conquer-display-none')
|
||||
}
|
||||
|
||||
private setError(error: string): void {
|
||||
const errorElement = this.getErrorElement();
|
||||
errorElement.classList.remove('conquer-display-none')
|
||||
errorElement.innerText = error
|
||||
}
|
||||
|
||||
private onSubmit(): void {
|
||||
const selectNodeType = this.getSelectNodeType();
|
||||
const inputNodeName = this.getInputNodeName();
|
||||
const textAreaNodeDescription = this.getTextAreaNodeDescription();
|
||||
const description = textAreaNodeDescription.value;
|
||||
const nodeName = inputNodeName.value;
|
||||
const selectedOptionsNodeType = selectNodeType.selectedOptions;
|
||||
if (selectedOptionsNodeType.length < 1) {
|
||||
this.setError('Debes selecionar un tipo de nodo.');
|
||||
return;
|
||||
}
|
||||
const selectedOptionNodeType = selectedOptionsNodeType[0];
|
||||
const nodeType = selectedOptionNodeType.value;
|
||||
if (nodeName.length < 5) {
|
||||
this.setError('Todos los nodos deben tener un nombre mayor a 4 caracteres.');
|
||||
return;
|
||||
}
|
||||
const urlNode = new URL('/conquer/node', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
fetch(urlNode, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
description: description,
|
||||
name: nodeName,
|
||||
type: nodeType,
|
||||
coordinates: this.coordinates,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = await res.json();
|
||||
} catch (error) {
|
||||
this.setError( 'Respuesta erronea del servidor.');
|
||||
return;
|
||||
}
|
||||
if (res.status !== 200) {
|
||||
this.setError(responseBody.error);
|
||||
return;
|
||||
}
|
||||
this.runCallbacks('close')
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||
import Conquer from '@burguillosinfo/conquer'
|
||||
|
||||
export default class NewTeamUI extends AbstractTopBarInterface {
|
||||
public getSubmitButton(): HTMLElement {
|
||||
const submitButton = this.getMainNode().querySelector('button.new-team-form-submit')
|
||||
if (submitButton === null || !(submitButton instanceof HTMLElement)) {
|
||||
Conquer.fail('SubmitButton is null');
|
||||
}
|
||||
return submitButton;
|
||||
}
|
||||
public getErrorElement(): HTMLElement {
|
||||
const errorElement = this.getMainNode().querySelector('p.conquer-error');
|
||||
if (errorElement === null || !(errorElement instanceof HTMLElement)) {
|
||||
Conquer.fail('No error element set');
|
||||
}
|
||||
return errorElement;
|
||||
}
|
||||
public getInputTeamName(): HTMLInputElement {
|
||||
const teamName = this.getMainNode().querySelector('input.conquer-team-name');
|
||||
if (teamName === null || !(teamName instanceof HTMLInputElement)) {
|
||||
Conquer.fail('TeamName is null');
|
||||
}
|
||||
return teamName;
|
||||
}
|
||||
public getInputTeamColor(): HTMLInputElement {
|
||||
const teamColor = this.getMainNode().querySelector('input.conquer-team-color');
|
||||
if (teamColor === null || !(teamColor instanceof HTMLInputElement)) {
|
||||
Conquer.fail('TeamColor is null');
|
||||
}
|
||||
return teamColor;
|
||||
}
|
||||
public getTextareaTeamDescription(): HTMLTextAreaElement {
|
||||
const teamDescription = this.getMainNode().querySelector('textarea.conquer-team-description')
|
||||
if (teamDescription === null || !(teamDescription instanceof HTMLTextAreaElement)) {
|
||||
Conquer.fail('TeamDescription is null');
|
||||
}
|
||||
return teamDescription
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
public run() {
|
||||
const mainNode = this.getMainNode()
|
||||
const form = this.getNodeFromTemplateId('conquer-new-team-form-creation-template')
|
||||
mainNode.append(form)
|
||||
this.getSubmitButton().addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
this.onSubmit();
|
||||
});
|
||||
form.classList.remove('conquer-display-none')
|
||||
mainNode.classList.remove('conquer-display-none')
|
||||
}
|
||||
|
||||
private setError(error: string): void {
|
||||
const errorElement = this.getErrorElement();
|
||||
errorElement.classList.remove('conquer-display-none')
|
||||
errorElement.innerText = error
|
||||
}
|
||||
|
||||
private onSubmit(): void {
|
||||
const inputTeamName = this.getInputTeamName();
|
||||
const textareaTeamDescription = this.getTextareaTeamDescription();
|
||||
const inputTeamColor = this.getInputTeamColor();
|
||||
const name = inputTeamName.value;
|
||||
const description = textareaTeamDescription.value;
|
||||
const color = inputTeamColor.value;
|
||||
if (name.length < 5) {
|
||||
this.setError('Todos los equipos deben tener un nombre mayor a 4 caracteres.');
|
||||
return;
|
||||
}
|
||||
const urlTeam = new URL('/conquer/team', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
fetch(urlTeam, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
description : description,
|
||||
name : name,
|
||||
color : color,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = await res.json();
|
||||
} catch (error) {
|
||||
this.setError( 'Respuesta erronea del servidor.');
|
||||
return;
|
||||
}
|
||||
if (res.status !== 200) {
|
||||
this.setError(responseBody.error);
|
||||
return;
|
||||
}
|
||||
this.runCallbacks('close')
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||
import Conquer from '@burguillosinfo/conquer'
|
||||
import MapNode from '@burguillosinfo/conquer/map-node'
|
||||
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||
|
||||
export default class NodeView extends AbstractTopBarInterface {
|
||||
private node: MapNode;
|
||||
private user: ConquerUser;
|
||||
private view: HTMLElement | null = null;
|
||||
|
||||
public getNode(): MapNode {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
private getNodeNameH2(): HTMLElement {
|
||||
const element = this.getMainNode().querySelector('h2.node-name');
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
Conquer.fail('h2.node-name is not a H2 or does not exist.');
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private getView(): HTMLElement {
|
||||
if (this.view === null) {
|
||||
const view = this.getNodeFromTemplateId('conquer-view-node-template')
|
||||
this.view = view;
|
||||
}
|
||||
return this.view;
|
||||
}
|
||||
|
||||
|
||||
private getNodeDescriptionParagraph(): HTMLElement {
|
||||
const element = this.getMainNode().querySelector('p.node-description');
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
Conquer.fail('p.node-description is not a P or does not exist.');
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
constructor(node: MapNode) {
|
||||
super()
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const user = await ConquerUser.getSelfUser();
|
||||
if (user === null) {
|
||||
this.runCallbacks('close');
|
||||
return;
|
||||
}
|
||||
this.user = user;
|
||||
const mainNode = this.getMainNode()
|
||||
this.runCallbacks('update-nodes');
|
||||
try {
|
||||
this.node = await this.node.fetch();
|
||||
} catch (error) {
|
||||
this.runCallbacks('close');
|
||||
}
|
||||
mainNode.append(this.getView())
|
||||
this.getNodeNameH2().innerText = this.node.getName();
|
||||
this.getNodeDescriptionParagraph().innerText = this.node.getDescription()
|
||||
+ "\n"
|
||||
+ (this.node.isNear()
|
||||
? 'Estas cerca y puedes interactuar con este sitio.'
|
||||
: 'Estás demasiado lejos para hacer nada aquí.');
|
||||
this.populateTeamData();
|
||||
if (this.node.isNear()) {
|
||||
await this.runIfNear();
|
||||
}
|
||||
this.getView().classList.remove('conquer-display-none')
|
||||
mainNode.classList.remove('conquer-display-none')
|
||||
}
|
||||
|
||||
private async populateTeamData() {
|
||||
const element = document.createElement('p');
|
||||
const team = await this.node.getTeam();
|
||||
(() => {
|
||||
if (team === null) {
|
||||
element.innerText = 'El nodo no pertenece a ningún equipo todavía.';
|
||||
return;
|
||||
}
|
||||
const spanText = document.createElement('span');
|
||||
spanText.innerText = 'Equipo: ';
|
||||
element.append(spanText);
|
||||
const spanCircle = document.createElement('span');
|
||||
spanCircle.classList.add('conquer-team-circle');
|
||||
spanCircle.style.backgroundColor = team.getColor();
|
||||
element.append(spanCircle);
|
||||
const spanTeamName = document.createElement('span');
|
||||
spanTeamName.style.color = team.getColor();
|
||||
spanTeamName.innerText = ' ' + team.getName();
|
||||
element.append(spanTeamName);
|
||||
})();
|
||||
this.getView().append(element);
|
||||
}
|
||||
|
||||
private async runIfNear(): Promise<void> {
|
||||
const userTeam = await this.user.getTeam();
|
||||
const nodeTeam = await this.node.getTeam();
|
||||
if (userTeam === null) {
|
||||
const paragraphNoTeam = document.createElement('p');
|
||||
paragraphNoTeam.innerText = 'Parece que no has seleccionado equipo aun,'
|
||||
+ ' pulsa el botón de seleccionar equipo para comenzar tu aventura,'
|
||||
+ ' si quieres cambiar de equipo en el futuro puedes hacerlo sin problemas.';
|
||||
this.getView().append(paragraphNoTeam);
|
||||
}
|
||||
|
||||
const selectTeamButton = document.createElement('button');
|
||||
selectTeamButton.innerText = 'Seleccionar equipo';
|
||||
selectTeamButton.addEventListener('click', () => {
|
||||
this.runCallbacks('open-select-team');
|
||||
this.runCallbacks('close');
|
||||
});
|
||||
|
||||
this.getView().append(selectTeamButton);
|
||||
if (await this.isOpposingNode()) {
|
||||
const conquerForTeamButton = document.createElement('button');
|
||||
conquerForTeamButton.innerText = 'Conquistar';
|
||||
conquerForTeamButton.addEventListener('click', () => {
|
||||
this.conquerThisNodeForTeam();
|
||||
});
|
||||
this.getView().append(conquerForTeamButton);
|
||||
}
|
||||
}
|
||||
|
||||
private async conquerThisNodeForTeam() {
|
||||
const urlNode = new URL('/conquer/node/' + this.node.getUUID() + '/try-conquer',
|
||||
window.location.protocol + '//'
|
||||
+ window.location.hostname + ':'
|
||||
+ window.location.port)
|
||||
const response = await fetch(urlNode, {
|
||||
method: 'POST',
|
||||
});
|
||||
this.runCallbacks('update-nodes');
|
||||
this.runCallbacks('close');
|
||||
}
|
||||
|
||||
private async isOpposingNode(): Promise<boolean> {
|
||||
const userTeam = await this.user.getTeam();
|
||||
const nodeTeam = await this.node.getTeam();
|
||||
if (userTeam === null) {
|
||||
return false;
|
||||
}
|
||||
if (nodeTeam === null) {
|
||||
return true;
|
||||
}
|
||||
if (nodeTeam.getUUID() !== userTeam.getUUID()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async isNodeFree(): Promise<boolean> {
|
||||
return await this.node.getTeam() === null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import Conquer from '@burguillosinfo/conquer';
|
||||
import ConquerUser from '@burguillosinfo/conquer/user';
|
||||
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface';
|
||||
import ConquerUserCurrentEnemy from '@burguillosinfo/conquer/user-current-enemy'
|
||||
|
||||
export default class SelectFightUI extends AbstractTopBarInterface {
|
||||
private enemies: ConquerUserCurrentEnemy[];
|
||||
private form: HTMLElement | null = null;
|
||||
|
||||
constructor(enemies: ConquerUserCurrentEnemy[]) {
|
||||
super();
|
||||
this.enemies = enemies;
|
||||
}
|
||||
|
||||
public async run(): Promise<void> {
|
||||
const user = await ConquerUser.getSelfUser()
|
||||
if (user === null) {
|
||||
this.runCallbacks('close')
|
||||
return
|
||||
}
|
||||
this.getMainNode().append(this.getForm());
|
||||
this.populateEnemies();
|
||||
this.getMainNode().classList.remove('conquer-display-none');
|
||||
}
|
||||
|
||||
private populateEnemies(): void {
|
||||
for (const enemy of this.enemies) {
|
||||
this.appendEnemy(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
private appendEnemy(enemy: ConquerUserCurrentEnemy) {
|
||||
const form = this.getForm();
|
||||
const enemyNode = this.getNodeFromTemplateId('conquer-select-fight-item-template');
|
||||
this.getNameEnemyNodeElement(enemyNode).innerText = enemy.getSpecies().getName();
|
||||
this.getLevelEnemyNodeElement(enemyNode).innerText = '' + enemy.getLevel();
|
||||
this.getImageEnemyNodeElement(enemyNode).src = enemy.getSpecies().getImage();
|
||||
form.append(enemyNode);
|
||||
}
|
||||
|
||||
private getImageEnemyNodeElement(enemyNode: HTMLElement): HTMLImageElement {
|
||||
const conquerImage = enemyNode.querySelector('.conquer-image');
|
||||
if (!(conquerImage instanceof HTMLImageElement)) {
|
||||
Conquer.fail('conquerImage is not HTMLImageElement.')
|
||||
}
|
||||
return conquerImage;
|
||||
}
|
||||
|
||||
private getLevelEnemyNodeElement(enemyNode: HTMLElement): HTMLElement {
|
||||
const conquerLevel = enemyNode.querySelector('.conquer-level');
|
||||
if (!(conquerLevel instanceof HTMLElement)) {
|
||||
Conquer.fail('conquerLevel is not HTMLElement.')
|
||||
}
|
||||
return conquerLevel;
|
||||
}
|
||||
|
||||
private getNameEnemyNodeElement(enemyNode: HTMLElement): HTMLElement {
|
||||
const conquerName = enemyNode.querySelector('.conquer-name');
|
||||
if (!(conquerName instanceof HTMLElement)) {
|
||||
Conquer.fail('conquerName is not HTMLElement.')
|
||||
}
|
||||
return conquerName;
|
||||
}
|
||||
|
||||
private getForm(): HTMLElement {
|
||||
if (this.form === null) {
|
||||
const form = this.getNodeFromTemplateId('conquer-select-fight-list-template')
|
||||
this.form = form;
|
||||
}
|
||||
return this.form;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||
import MapNode from '@burguillosinfo/conquer/map-node'
|
||||
import ConquerTeam from '@burguillosinfo/conquer/team';
|
||||
|
||||
export default class SelectTeamUI extends AbstractTopBarInterface {
|
||||
private node: MapNode;
|
||||
private user: ConquerUser;
|
||||
private form: HTMLElement | null = null;
|
||||
|
||||
constructor(node: MapNode) {
|
||||
super();
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public async run(): Promise<void> {
|
||||
const user = await ConquerUser.getSelfUser()
|
||||
if (user === null) {
|
||||
this.runCallbacks('close')
|
||||
return
|
||||
}
|
||||
this.user = user
|
||||
await this.populateTeams();
|
||||
this.getForm().classList.remove('conquer-display-none');
|
||||
this.getMainNode().append(this.getForm());
|
||||
this.getMainNode().classList.remove('conquer-display-none');
|
||||
}
|
||||
|
||||
private async populateTeams() {
|
||||
const teams = await ConquerTeam.getTeams();
|
||||
for (const team of teams) {
|
||||
this.populateTeam(team);
|
||||
}
|
||||
}
|
||||
|
||||
private populateTeam(team: ConquerTeam) {
|
||||
const teamDiv = this.getNodeFromTemplateId('conquer-team-to-select-template')
|
||||
const nameParagraph = teamDiv.querySelector('p.conquer-name');
|
||||
const descriptionParagraph = teamDiv.querySelector('p.conquer-description');
|
||||
const submit = teamDiv.querySelector('button.conquer-submit');
|
||||
if (!(nameParagraph instanceof HTMLParagraphElement)
|
||||
|| !(descriptionParagraph instanceof HTMLParagraphElement)
|
||||
|| !(submit instanceof HTMLButtonElement)) {
|
||||
Conquer.fail('Select team name inclusive or description container are not correctly defined in template.');
|
||||
}
|
||||
nameParagraph.innerText = team.getName();
|
||||
descriptionParagraph.innerText = team.getDescription();
|
||||
nameParagraph.style.color = team.getColor();
|
||||
submit.addEventListener('click', async () => {
|
||||
this.onSelectTeam(team);
|
||||
});
|
||||
this.getForm().append(teamDiv);
|
||||
}
|
||||
|
||||
private async onSelectTeam(team: ConquerTeam) {
|
||||
const urlTeam = new URL('/conquer/user/team',
|
||||
window.location.protocol + '//'
|
||||
+ window.location.hostname + ':'
|
||||
+ window.location.port);
|
||||
const response = await fetch(urlTeam, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
team: team.getUUID(),
|
||||
node: this.node.getUUID(),
|
||||
}),
|
||||
});
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = await response.json();
|
||||
if (response.status !== 200) {
|
||||
console.error(responseBody.error);
|
||||
return;
|
||||
}
|
||||
this.runCallbacks('update-nodes');
|
||||
this.runCallbacks('close')
|
||||
} catch (error) {
|
||||
console.error('Error parsing json', error);
|
||||
}
|
||||
}
|
||||
|
||||
private getForm(): HTMLElement {
|
||||
if (this.form === null) {
|
||||
const form = this.getNodeFromTemplateId('conquer-select-team-list-template')
|
||||
this.form = form;
|
||||
}
|
||||
return this.form;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||
|
||||
export default class SelfPlayerUI extends AbstractTopBarInterface {
|
||||
private selfPlayer: ConquerUser | null = null
|
||||
private userWelcome: HTMLElement | null = null
|
||||
private isExplorerModeEnabled: boolean;
|
||||
private userTeamData: HTMLElement | null = null;
|
||||
|
||||
constructor(isExplorerModeEnabled: boolean) {
|
||||
super();
|
||||
this.isExplorerModeEnabled = isExplorerModeEnabled;
|
||||
}
|
||||
|
||||
public async run(): Promise<void> {
|
||||
const selfPlayerNode = this.getMainNode()
|
||||
const user = await ConquerUser.getSelfUser()
|
||||
if (user === null) {
|
||||
this.runCallbacks('close')
|
||||
return
|
||||
}
|
||||
this.selfPlayer = user
|
||||
this.populateWelcome()
|
||||
this.populateCreateNodeOption()
|
||||
this.populateToggleExplorerModeOption();
|
||||
this.populateCreateTeamButton();
|
||||
await this.populateUserTeamData();
|
||||
selfPlayerNode.classList.remove('conquer-display-none')
|
||||
}
|
||||
|
||||
private populateToggleExplorerModeOption(): void {
|
||||
const toggleExplorerModeButton = document.createElement('button');
|
||||
this.setTextToggleExplorerModeButton(toggleExplorerModeButton);
|
||||
toggleExplorerModeButton.addEventListener('click', () => {
|
||||
(() => {
|
||||
if (this.isExplorerModeEnabled) {
|
||||
this.runCallbacks('disable-explorer-mode');
|
||||
return;
|
||||
}
|
||||
this.runCallbacks('enable-explorer-mode');
|
||||
})();
|
||||
this.runCallbacks('close');
|
||||
});
|
||||
const toggleExplorerModeInterface = this.generateInterfaceElementCentered()
|
||||
toggleExplorerModeInterface.appendChild(toggleExplorerModeButton)
|
||||
this.getMainNode().appendChild(toggleExplorerModeInterface)
|
||||
|
||||
}
|
||||
|
||||
private populateCreateTeamButton(): void {
|
||||
// Only admins can create teams.
|
||||
if (!this.selfPlayer?.isAdmin()) {
|
||||
return;
|
||||
}
|
||||
const createTeamButton = document.createElement('button');
|
||||
createTeamButton.innerText = 'Crea un nuevo equipo';
|
||||
createTeamButton.addEventListener('click', () => {
|
||||
this.runCallbacks('open-create-team');
|
||||
this.runCallbacks('close');
|
||||
});
|
||||
const createTeamButtonInterface = this.generateInterfaceElementCentered()
|
||||
createTeamButtonInterface.append(createTeamButton);
|
||||
this.getMainNode().appendChild(createTeamButtonInterface);
|
||||
}
|
||||
|
||||
private setTextToggleExplorerModeButton(button: HTMLElement): void {
|
||||
if (this.isExplorerModeEnabled) {
|
||||
button.innerText = 'Desactivar movimiento libre en el mapa.';
|
||||
return;
|
||||
}
|
||||
button.innerText = 'Activar movimiento libre en el mapa.';
|
||||
}
|
||||
|
||||
private populateCreateNodeOption() {
|
||||
// Only admins can create nodes.
|
||||
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.getMainNode().appendChild(createNodeButtonInterface)
|
||||
}
|
||||
|
||||
private async getUserTeamData(): Promise<HTMLElement> {
|
||||
if (this.userTeamData !== null) {
|
||||
return this.userTeamData;
|
||||
}
|
||||
const element = document.createElement('p');
|
||||
this.userTeamData = element;
|
||||
if (this.selfPlayer === null) {
|
||||
throw new Error('User still not set')
|
||||
}
|
||||
const team = await this.selfPlayer.getTeam();
|
||||
if (team === null) {
|
||||
element.innerText = 'No tienes equipo aun,'
|
||||
+ ' ve al nodo más cercano para unirte a un equipo.';
|
||||
return this.userTeamData;
|
||||
}
|
||||
const spanText = document.createElement('span');
|
||||
spanText.innerText = 'Equipo: ';
|
||||
element.append(spanText);
|
||||
const spanCircle = document.createElement('span');
|
||||
spanCircle.classList.add('conquer-team-circle');
|
||||
spanCircle.style.backgroundColor = team.getColor();
|
||||
element.append(spanCircle);
|
||||
const spanTeamName = document.createElement('span');
|
||||
spanTeamName.style.color = team.getColor();
|
||||
spanTeamName.innerText = ' ' + team.getName();
|
||||
element.append(spanTeamName);
|
||||
return this.userTeamData;
|
||||
}
|
||||
|
||||
private async populateUserTeamData(): Promise<void> {
|
||||
const userTeamData = await this.getUserTeamData();
|
||||
const userTeamDataInterface = this.generateInterfaceElementCentered();
|
||||
userTeamDataInterface.append(userTeamData);
|
||||
this.getMainNode().append(userTeamDataInterface);
|
||||
}
|
||||
|
||||
private populateWelcome(): void {
|
||||
const userWelcome = this.getUserWelcome()
|
||||
const userWelcomeInterface = this.generateInterfaceElementCentered();
|
||||
userWelcomeInterface.appendChild(userWelcome)
|
||||
this.getMainNode().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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
import Conquer from '@burguillosinfo/conquer'
|
||||
import ConquerInterfaceManager from '@burguillosinfo/conquer/interface-manager'
|
||||
import LoginUI from '@burguillosinfo/conquer/interface/login'
|
||||
|
||||
export type ConquerLoginEventCallback = () => void
|
||||
|
||||
export default class Login {
|
||||
private conquerLogin: HTMLDivElement
|
||||
private conquerInterfaceManager: ConquerInterfaceManager
|
||||
private cachedIsLoggedIn: boolean | null = null
|
||||
|
||||
constructor(conquerInterfaceManager: ConquerInterfaceManager) {
|
||||
this.conquerInterfaceManager = conquerInterfaceManager
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
this.loopCheckLogin()
|
||||
}
|
||||
|
||||
public async onRegisterRequest(loginUI: LoginUI, username: string, password: string, repeatPassword: string): Promise<void> {
|
||||
const urlUser = new URL('/conquer/user', window.location.protocol +
|
||||
'//' + window.location.hostname + ':' + window.location.port)
|
||||
let responseJson
|
||||
let status
|
||||
try {
|
||||
const response = await fetch(urlUser, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password,
|
||||
repeat_password: repeatPassword
|
||||
})
|
||||
})
|
||||
responseJson = await response.json()
|
||||
status = response.status
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
loginUI.addNewRegisterError('El servidor ha enviado datos inesperados.')
|
||||
return
|
||||
}
|
||||
if (status !== 200) {
|
||||
loginUI.addNewRegisterError(responseJson.error)
|
||||
return
|
||||
}
|
||||
loginUI.addNewLoginSuccessText(`Usuario registrado ${username}.`)
|
||||
loginUI.goToLogin()
|
||||
}
|
||||
|
||||
private async loopCheckLogin(): Promise<void> {
|
||||
window.setInterval(() => {
|
||||
this.isLogged().then((isLogged) => {
|
||||
if (isLogged) {
|
||||
if (this.cachedIsLoggedIn !== true) {
|
||||
this.cachedIsLoggedIn = true;
|
||||
this.onLoginSuccess();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.cachedIsLoggedIn !== false) {
|
||||
this.cachedIsLoggedIn = false;
|
||||
this.onLogout()
|
||||
}
|
||||
})
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
private async onLogout(): Promise<void> {
|
||||
const interfaceManager = this.conquerInterfaceManager
|
||||
const loginUI = new LoginUI(this)
|
||||
for (const callback of this.callbacks.logout) {
|
||||
callback();
|
||||
}
|
||||
loginUI.on('close', () => {
|
||||
interfaceManager.remove(loginUI);
|
||||
})
|
||||
interfaceManager.push(loginUI)
|
||||
}
|
||||
|
||||
private callbacks: Record<string, Array<ConquerLoginEventCallback>> = {}
|
||||
public async on(name: string, callback: ConquerLoginEventCallback) {
|
||||
if (this.callbacks[name] === undefined) {
|
||||
this.callbacks[name] = []
|
||||
}
|
||||
this.callbacks[name].push(callback)
|
||||
}
|
||||
|
||||
private async onLoginSuccess(): Promise<void> {
|
||||
this.cachedIsLoggedIn = true
|
||||
for (const callback of this.callbacks.login) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
public async onLoginRequested(loginUI: LoginUI, username: string, password: string): Promise<void> {
|
||||
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)
|
||||
loginUI.addNewLoginError('El servidor ha enviado datos inesperados.')
|
||||
return
|
||||
}
|
||||
if (status !== 200) {
|
||||
loginUI.addNewLoginError(responseJson.error)
|
||||
return
|
||||
}
|
||||
loginUI.unsetLoginAndRegisterErrors()
|
||||
const isLogged = await this.isLogged()
|
||||
if (isLogged) {
|
||||
this.onLoginSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
public async isLogged(): Promise<boolean> {
|
||||
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
let status
|
||||
try {
|
||||
const response = await fetch(urlUser)
|
||||
status = response.status
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
return status === 200
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||
import Style from 'ol/style/Style'
|
||||
import Feature from 'ol/Feature'
|
||||
import CircleStyle from 'ol/style/Circle'
|
||||
import Point from 'ol/geom/Point'
|
||||
import Fill from 'ol/style/Fill'
|
||||
import Stroke from 'ol/style/Stroke'
|
||||
import InterfaceManager from '@burguillosinfo/conquer/interface-manager'
|
||||
import NodeView from '@burguillosinfo/conquer/interface/node-view'
|
||||
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||
import SelectTeamUI from '@burguillosinfo/conquer/interface/select-team';
|
||||
import ConquerTeam from '@burguillosinfo/conquer/team';
|
||||
|
||||
@JsonObject()
|
||||
export default class MapNode {
|
||||
private feature: Feature | null = null;
|
||||
private callbacks: Record<string, Array<() => void>> = {}
|
||||
private cachedTeam: ConquerTeam | null = null;
|
||||
|
||||
constructor(
|
||||
@JsonProperty() private uuid: string,
|
||||
@JsonProperty() private coordinate_1: number,
|
||||
@JsonProperty() private coordinate_2: number,
|
||||
@JsonProperty() private type: string,
|
||||
@JsonProperty() private name: string,
|
||||
@JsonProperty() private description: string,
|
||||
@JsonProperty() private kind: string,
|
||||
@JsonProperty() private is_near: boolean,
|
||||
@JsonProperty() private team: string,
|
||||
) {
|
||||
}
|
||||
|
||||
public async getTeam(): Promise<ConquerTeam | null> {
|
||||
if (this.cachedTeam === null) {
|
||||
if (this.team === null) {
|
||||
return null;
|
||||
}
|
||||
this.cachedTeam = await ConquerTeam.getTeam(this.team);
|
||||
}
|
||||
return this.cachedTeam;
|
||||
}
|
||||
|
||||
|
||||
public async fetch(): Promise<MapNode> {
|
||||
const urlNode = new URL('/conquer/node/' + this.uuid, window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
const response = await fetch(urlNode);
|
||||
let responseBody;
|
||||
const errorThrow = new Error('Unable to fetch node updated.');
|
||||
try {
|
||||
responseBody = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error parseando json: ' + responseBody);
|
||||
console.error(error);
|
||||
throw errorThrow;
|
||||
}
|
||||
if (response.status !== 200) {
|
||||
console.error(responseBody.error);
|
||||
throw errorThrow;
|
||||
}
|
||||
const node = JsonSerializer.deserialize(responseBody, MapNode);
|
||||
if (!(node instanceof MapNode)) {
|
||||
console.error('Unexpected JSON value for MapNode.');
|
||||
throw errorThrow;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public click(interfaceManager: InterfaceManager): void {
|
||||
const viewNodeInterface = new NodeView(this);
|
||||
viewNodeInterface.on('close', () => {
|
||||
interfaceManager.remove(viewNodeInterface);
|
||||
});
|
||||
viewNodeInterface.on('update-nodes', () => {
|
||||
this.runCallbacks('update-nodes');
|
||||
});
|
||||
viewNodeInterface.on('open-select-team', () => {
|
||||
this.openSelectTeam(interfaceManager);
|
||||
});
|
||||
interfaceManager.push(viewNodeInterface);
|
||||
this.runCallbacks('click');
|
||||
}
|
||||
|
||||
public openSelectTeam(interfaceManager: InterfaceManager): void {
|
||||
const selectTeamUI = new SelectTeamUI(this);
|
||||
selectTeamUI.on('update-nodes', () => {
|
||||
this.runCallbacks('update-nodes');
|
||||
});
|
||||
selectTeamUI.on('close', () => {
|
||||
interfaceManager.remove(selectTeamUI);
|
||||
});
|
||||
interfaceManager.push(selectTeamUI);
|
||||
}
|
||||
|
||||
public on(eventName: string, callback: () => void): void {
|
||||
if (this.callbacks[eventName] === undefined) {
|
||||
this.callbacks[eventName] = []
|
||||
}
|
||||
this.callbacks[eventName].push(callback)
|
||||
}
|
||||
|
||||
protected runCallbacks(eventName: string) {
|
||||
const callbacks = this.callbacks[eventName];
|
||||
if (callbacks === undefined) {
|
||||
return
|
||||
}
|
||||
for (const callback of callbacks) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
public getType(): string {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public isNear(): boolean {
|
||||
return this.is_near;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return 'node-' + this.uuid;
|
||||
}
|
||||
|
||||
public getUUID(): string {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
public getFeature(): Feature {
|
||||
if (this.feature === null) {
|
||||
this.feature = new Feature({
|
||||
geometry: new Point([this.coordinate_1, this.coordinate_2]),
|
||||
type: 'node-' + this.uuid,
|
||||
})
|
||||
}
|
||||
return this.feature;
|
||||
}
|
||||
|
||||
public async getStyle(): Promise<Style> {
|
||||
const team = await this.getTeam();
|
||||
let color = 'white';
|
||||
if (team !== null) {
|
||||
color = team.getColor();
|
||||
}
|
||||
return new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 14,
|
||||
fill: new Fill({color: color}),
|
||||
stroke: new Stroke({
|
||||
color: 'black',
|
||||
width: 5,
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
enum MapState {
|
||||
NOTHING = 0x0,
|
||||
NORMAL = 0x1,
|
||||
FREE_MOVE = 0x2,
|
||||
FREE_ROTATION = 0x4,
|
||||
CREATE_NODE = 0x8,
|
||||
SELECT_WHERE_TO_CREATE_NODE = 0x10,
|
||||
FILLING_FORM_CREATE_NODE = 0x20,
|
||||
}
|
||||
|
||||
export default MapState
|
|
@ -0,0 +1,6 @@
|
|||
import { JsonSerializer, throwError } from 'typescript-json-serializer';
|
||||
|
||||
export default new JsonSerializer({
|
||||
errorCallback: throwError,
|
||||
additionalPropertiesPolicy: 'disallow',
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||
|
||||
@JsonObject()
|
||||
export default class Specie {
|
||||
@JsonProperty()
|
||||
private id: string;
|
||||
|
||||
@JsonProperty()
|
||||
private name: string;
|
||||
|
||||
@JsonProperty()
|
||||
private image: string;
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public getImage(): string {
|
||||
return this.image;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||
import Conquer from '@burguillosinfo/conquer'
|
||||
|
||||
@JsonObject()
|
||||
export default class ConquerTeam {
|
||||
@JsonProperty()
|
||||
private kind: string;
|
||||
@JsonProperty()
|
||||
private uuid: string;
|
||||
@JsonProperty()
|
||||
private name: string;
|
||||
@JsonProperty()
|
||||
private description: string;
|
||||
@JsonProperty()
|
||||
private points: number;
|
||||
@JsonProperty()
|
||||
private color: string;
|
||||
|
||||
public getUUID(): string {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public getColor(): string {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
constructor(uuid: string, name: string, description: string, points: number, color: string) {
|
||||
this.kind = 'ConquerTeam';
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.points = points;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public static async getTeams(): Promise<ConquerTeam[]> {
|
||||
const urlTeam = new URL('/conquer/teams', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
try {
|
||||
const response = await fetch(urlTeam)
|
||||
if (response.status !== 200) {
|
||||
throw new Error('Invalid response fetching teams.')
|
||||
}
|
||||
const teamData = await response.json()
|
||||
const teams = JsonSerializer.deserialize(teamData, ConquerTeam);
|
||||
if (teams === undefined || teams === null) {
|
||||
Conquer.fail('Teams cannot be null, server error.');
|
||||
}
|
||||
if (!(teams instanceof Array)) {
|
||||
throw new Error('Unable to parse team.');
|
||||
}
|
||||
const teamsSanitized: ConquerTeam[] = [];
|
||||
for (const team of teams) {
|
||||
if (!(team instanceof ConquerTeam)) {
|
||||
console.error('Received null team from server, fix this error.');
|
||||
continue;
|
||||
}
|
||||
teamsSanitized.push(team);
|
||||
}
|
||||
return teamsSanitized;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error('Unable to fetch Teams.');
|
||||
}
|
||||
}
|
||||
public static async getTeam(uuid: string): Promise<ConquerTeam> {
|
||||
const urlTeam = new URL('/conquer/team/' + uuid, window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
try {
|
||||
const response = await fetch(urlTeam)
|
||||
if (response.status !== 200) {
|
||||
throw new Error('Invalid response fetching team.')
|
||||
}
|
||||
const teamData = await response.json()
|
||||
let team = JsonSerializer.deserialize(teamData, ConquerTeam);
|
||||
if (team === undefined) {
|
||||
team = null;
|
||||
}
|
||||
if (!(team instanceof ConquerTeam)) {
|
||||
throw new Error('Unable to parse team.');
|
||||
}
|
||||
return team;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error('Unable to fetch Team.');
|
||||
}
|
||||
}
|
||||
|
||||
public static async getSelfTeam(): Promise<ConquerTeam | null> {
|
||||
const urlTeam = new URL('/conquer/user/team', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||
try {
|
||||
const response = await fetch(urlTeam)
|
||||
if (response.status !== 200) {
|
||||
throw new Error('Invalid response fetching team.')
|
||||
}
|
||||
const teamData = await response.json()
|
||||
let team = JsonSerializer.deserialize(teamData, ConquerTeam);
|
||||
if (team === undefined) {
|
||||
team = null;
|
||||
}
|
||||
if (team !== null && !(team instanceof ConquerTeam)) {
|
||||
throw new Error('Unable to parse team.');
|
||||
}
|
||||
return team;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error('Unable to fetch Team.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import Conquer from '@burguillosinfo/conquer';
|
||||
|
||||
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||
import Specie from '@burguillosinfo/conquer/specie';
|
||||
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||
|
||||
@JsonObject()
|
||||
export default class ConquerUserCurrentEnemy {
|
||||
@JsonProperty()
|
||||
private uuid: string;
|
||||
|
||||
@JsonProperty()
|
||||
private species: Specie;
|
||||
|
||||
@JsonProperty()
|
||||
private level: number;
|
||||
|
||||
@JsonProperty()
|
||||
private max_health: number;
|
||||
|
||||
public getUUID(): string {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
public getSpecies(): Specie {
|
||||
return this.species;
|
||||
}
|
||||
|
||||
public getLevel(): number {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
public getMaxHealth(): number {
|
||||
return this.max_health;
|
||||
}
|
||||
|
||||
public static async getGlobalEnemies(): Promise<ConquerUserCurrentEnemy[] | null> {
|
||||
const urlEnemies = new URL('/conquer/user/enemies/global', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port);
|
||||
const response = await fetch(urlEnemies);
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = await response.json();
|
||||
if (response.status !== 200) {
|
||||
console.error(responseBody.error);
|
||||
return null;
|
||||
}
|
||||
const enemiesRaw = JsonSerializer.deserialize(responseBody, ConquerUserCurrentEnemy);
|
||||
const enemiesReturnArray: ConquerUserCurrentEnemy[] = [];
|
||||
if (!(enemiesRaw instanceof Array)) {
|
||||
console.error('Incorrect type retrieved from ' + urlEnemies);
|
||||
return null;
|
||||
}
|
||||
for (const enemy of enemiesRaw) {
|
||||
if (!(enemy instanceof ConquerUserCurrentEnemy)) {
|
||||
console.error('Incorrect type for enemy, maybe null or undef.', enemy);
|
||||
return null;
|
||||
}
|
||||
enemiesReturnArray.push(enemy);
|
||||
}
|
||||
return enemiesReturnArray;
|
||||
} catch(error) {
|
||||
console.error(error, 'Invalid response from server seeking for possible battles.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||
import ConquerTeam from '@burguillosinfo/conquer/team';
|
||||
|
||||
export interface UserData {
|
||||
is_admin: number
|
||||
kind: string
|
||||
last_activity?: string
|
||||
registration_date?: string
|
||||
username: string
|
||||
uuid: string
|
||||
}
|
||||
|
||||
@JsonObject()
|
||||
export default class ConquerUser {
|
||||
@JsonProperty()
|
||||
private is_admin: boolean;
|
||||
@JsonProperty()
|
||||
private kind: string;
|
||||
@JsonProperty()
|
||||
private last_activity: string | null;
|
||||
@JsonProperty()
|
||||
private registration_date: string | null;
|
||||
@JsonProperty()
|
||||
private username: string;
|
||||
@JsonProperty()
|
||||
private uuid: string;
|
||||
@JsonProperty()
|
||||
private team: string | null;
|
||||
private cachedTeam: ConquerTeam | null = null;
|
||||
|
||||
constructor(kind: string, uuid: string, username: string, is_admin = false, registration_date: string | null = null, last_activity: string | null = null) {
|
||||
this.kind = kind;
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
this.is_admin = is_admin;
|
||||
this.registration_date = registration_date;
|
||||
this.last_activity = last_activity;
|
||||
}
|
||||
|
||||
public async getTeam(): Promise<ConquerTeam | null> {
|
||||
if (this.cachedTeam === null) {
|
||||
if (this.team === null) {
|
||||
return null;
|
||||
}
|
||||
this.cachedTeam = await ConquerTeam.getTeam(this.team);
|
||||
}
|
||||
return this.cachedTeam;
|
||||
}
|
||||
|
||||
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()
|
||||
const user = JsonSerializer.deserialize(userData, ConquerUser);
|
||||
if (!(user instanceof ConquerUser)) {
|
||||
throw new Error('Unable to parse user.');
|
||||
}
|
||||
return user;
|
||||
} 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.is_admin
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
export default class ConquerWebSocket {
|
||||
private webSocket: WebSocket | null = null
|
||||
private socketReady = false
|
||||
|
||||
private getWebSocket(): WebSocket {
|
||||
if (this.webSocket !== null && this.socketReady) {
|
||||
return this.webSocket
|
||||
}
|
||||
this.webSocket = new WebSocket(`wss://${window.location.hostname}:${window.location.port}/conquer/websocket`)
|
||||
this.webSocket.addEventListener('close', (event) => {
|
||||
this.onSocketClose(event)
|
||||
})
|
||||
this.webSocket.addEventListener('error', (event) => {
|
||||
this.onSocketClose(event)
|
||||
})
|
||||
this.webSocket.addEventListener('open', (event) => {
|
||||
this.onSocketOpen(event)
|
||||
})
|
||||
return this.webSocket
|
||||
}
|
||||
|
||||
private onSocketOpen(event: Event) {
|
||||
this.socketReady = true
|
||||
}
|
||||
|
||||
private onSocketClose(event: Event) {
|
||||
this.socketReady = false
|
||||
console.error(event)
|
||||
}
|
||||
}
|
|
@ -1,12 +1,23 @@
|
|||
"use strict";
|
||||
import Tablesort from 'tablesort';
|
||||
import Conquer from '@burguillosinfo/conquer/index';
|
||||
import CarouselAd from '@burguillosinfo/carousel-ad'
|
||||
window.Tablesort = require('tablesort');
|
||||
require('tablesort/src/sorts/tablesort.number');
|
||||
|
||||
let fakeSearchInput
|
||||
let searchMobile
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
onDomContentLoaded();
|
||||
}, false);
|
||||
|
||||
function onDomContentLoaded() {
|
||||
const path = window.location.pathname
|
||||
if (path.match(/^(?:\/)?conquer(?:$|\/)/)) {
|
||||
Conquer.start();
|
||||
return
|
||||
}
|
||||
const menu_expand = document.querySelector('a.menu-expand');
|
||||
const mobile_foldable = document.querySelector('nav.mobile-foldable');
|
||||
const transparentFullscreenHide = document.querySelector('div.transparent-fullscreen-hide');
|
||||
|
@ -64,8 +75,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
fakeSearchInput = searchMobile.querySelector('input')
|
||||
addListenersSearch()
|
||||
}
|
||||
}, false);
|
||||
|
||||
}
|
||||
function fillFarmaciaGuardia() {
|
||||
const farmaciaName = document.querySelector('#farmacia-name');
|
||||
const farmaciaAddress = document.querySelector('#farmacia-address');
|
||||
|
@ -121,14 +131,6 @@ function addListenersSearch() {
|
|||
}
|
||||
const nextResult = searchInPage.querySelector('a.down');
|
||||
const prevResult = searchInPage.querySelector('a.up');
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key.toLowerCase() === "f" && e.ctrlKey) {
|
||||
openAllDetails()
|
||||
}
|
||||
});
|
||||
window.addEventListener("blur", (e) => {
|
||||
openAllDetails()
|
||||
})
|
||||
if (nextResult !== null && prevResult !== null) {
|
||||
nextResult.addEventListener('click', () => {
|
||||
searchInWebsite(fakeSearchInput.value, true);
|
||||
|
@ -157,7 +159,6 @@ function addListenersSearch() {
|
|||
function searchInWebsite(value, isToBottom) {
|
||||
window.find(value, false, !isToBottom, true)
|
||||
const selection = window.getSelection()
|
||||
openAllDetails()
|
||||
if (selection.anchorNode === null) {
|
||||
const pageContents = document.querySelector('div.page-contents');
|
||||
pageContents.focus()
|
||||
|
@ -177,12 +178,6 @@ function searchInWebsite(value, isToBottom) {
|
|||
}
|
||||
}
|
||||
|
||||
function openAllDetails() {
|
||||
for (const detail of document.querySelectorAll('details')) {
|
||||
detail.open = true
|
||||
}
|
||||
}
|
||||
|
||||
function _getOffsetTopWithNParent(element, nParent, _carry = 0) {
|
||||
if (element === null) {
|
||||
return null;
|
||||
|
|
|
@ -7,6 +7,7 @@ use Mojo::Base 'Mojolicious', -signatures;
|
|||
# This method will run once at server start
|
||||
sub startup ($self) {
|
||||
my $metrics = BurguillosInfo::Controller::Metrics->new;
|
||||
$self->sessions->default_expiration(0);
|
||||
$self->hook(
|
||||
around_dispatch => sub {
|
||||
my $next = shift;
|
||||
|
@ -19,26 +20,66 @@ sub startup ($self) {
|
|||
);
|
||||
push @{ $self->commands->namespaces }, 'BurguillosInfo::Command';
|
||||
$self->hook(
|
||||
before_render => sub($c, $args) {
|
||||
before_render => sub ( $c, $args ) {
|
||||
my $current_route = $c->url_for;
|
||||
$c->stash(current_route => $current_route);
|
||||
my $is_android = $c->req->headers->user_agent =~ /android/i;
|
||||
$c->stash(is_android => $is_android);
|
||||
$c->stash( current_route => $current_route );
|
||||
my $is_android = $c->req->headers->user_agent =~ /android/i;
|
||||
$c->stash( is_android => $is_android );
|
||||
my $onion_base_url = $self->config->{onion_base_url};
|
||||
my $base_url = $self->config->{base_url};
|
||||
if (!defined $onion_base_url) {
|
||||
my $base_url = $self->config->{base_url};
|
||||
if ( !defined $onion_base_url ) {
|
||||
return;
|
||||
}
|
||||
$current_route =~ s/^$base_url//;
|
||||
$c->res->headers->header('Onion-Location' => $onion_base_url.$current_route);
|
||||
$c->res->headers->header(
|
||||
'Onion-Location' => $onion_base_url . $current_route );
|
||||
}
|
||||
);
|
||||
my $config = $self->plugin('JSONConfig');
|
||||
$self->config(
|
||||
hypnotoad => { proxy => 1, listen => [$self->config('listen') // 'http://localhost:3000'] } );
|
||||
hypnotoad => {
|
||||
proxy => 1,
|
||||
listen => [ $self->config('listen') // 'http://localhost:3000' ]
|
||||
}
|
||||
);
|
||||
$self->config( css_version => int( rand(10000) ) );
|
||||
$self->secrets( $self->config->{secrets} );
|
||||
|
||||
$self->helper(
|
||||
current_user => sub ($c) {
|
||||
use BurguillosInfo::Schema;
|
||||
$self->session(expiration => 0);
|
||||
my $user_uuid = $c->session->{conquer}{user};
|
||||
if ( !defined $user_uuid ) {
|
||||
return;
|
||||
}
|
||||
my $user_resultset =
|
||||
BurguillosInfo::Schema->Schema->resultset('ConquerUser');
|
||||
my @user_candidates =
|
||||
$user_resultset->search( { uuid => $user_uuid } );
|
||||
my $user = $user_candidates[0];
|
||||
|
||||
# Just to make clear what happens if there is no user we return.
|
||||
if ( !defined $user ) {
|
||||
return;
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
);
|
||||
$self->helper(
|
||||
set_current_user => sub ( $c, $user ) {
|
||||
$self->session(expiration => 0);
|
||||
if ( !defined $user
|
||||
|| !$user->can('uuid')
|
||||
|| !$user->can('get_from_storage') )
|
||||
{
|
||||
die "$user is not a valid user for it's usage in a session.";
|
||||
}
|
||||
$user = $user->get_from_storage;
|
||||
$c->session->{conquer}{user} = $user->uuid;
|
||||
}
|
||||
);
|
||||
|
||||
# Router
|
||||
my $r = $self->routes;
|
||||
|
||||
|
@ -48,12 +89,29 @@ sub startup ($self) {
|
|||
$r->get('/sitemap.xml')->to('Sitemap#sitemap');
|
||||
$r->get('/robots.txt')->to('Robots#robots');
|
||||
|
||||
# $r->get('/:post')->to('Page#post');
|
||||
$r->get('/stats')->to('Metrics#stats');
|
||||
$r->get('/conquer')->to('Conquer#index');
|
||||
$r->put('/conquer/user')->to('UserConquer#create');
|
||||
$r->get('/conquer/user/team')->to('UserConquer#getSelfTeam');
|
||||
$r->post('/conquer/user/team')->to('UserConquer#setTeamForUser');
|
||||
$r->post('/conquer/user/coordinates')->to('UserConquer#setCoordinates');
|
||||
$r->get('/conquer/team/<uuid>')->to('ConquerTeam#get');
|
||||
$r->put('/conquer/team')->to('ConquerTeam#put');
|
||||
$r->get('/conquer/teams')->to('ConquerTeam#getAll');
|
||||
$r->put('/conquer/node')->to('ConquerNode#create');
|
||||
$r->get('/conquer/node/near')->to('ConquerNode#nearbyNodes');
|
||||
$r->get('/conquer/user/enemies/global')->to('ConquerUserCurrentEnemy#listEnemiesGlobal');
|
||||
$r->post('/conquer/user/enemies/fight')->to('ConquerUserCurrentEnemy#fightEnemy');
|
||||
$r->get('/conquer/node/<uuid>')->to('ConquerNode#get');
|
||||
$r->post('/conquer/node/<uuid>/try-conquer')->to('ConquerNode#tryConquer');
|
||||
$r->get('/conquer/user')->to('UserConquer#get_self');
|
||||
$r->post('/conquer/user/login')->to('UserConquer#login');
|
||||
$r->get('/conquer/tile/<zoom>/<x>/<y>.png')->to('ConquerTile#tile');
|
||||
$r->get('/search.json')->to('Search#search');
|
||||
$r->get('/farmacia-guardia.json')->to('FarmaciaGuardia#current');
|
||||
$r->get('/<:category>.rss')->to('Page#category_rss');
|
||||
$r->get('/:category_slug/atributo/<:attribute_slug>-preview.png')->to('Attribute#get_attribute_preview');
|
||||
$r->get('/:category_slug/atributo/<:attribute_slug>-preview.png')
|
||||
->to('Attribute#get_attribute_preview');
|
||||
$r->get('/:category_slug/atributo/:attribute_slug')->to('Attribute#get');
|
||||
$r->get('/<:category>-preview.png')->to('Page#get_category_preview');
|
||||
$r->get('/:category')->to('Page#category');
|
||||
|
|
|
@ -47,20 +47,17 @@ sub get_next ( $self, $current_ad_number = undef ) {
|
|||
if ( !defined $current_ad_number ) {
|
||||
$current_ad_number = 0;
|
||||
}
|
||||
my $ad;
|
||||
while (!defined $ad || $ad->id eq $current_ad_number) {
|
||||
$ad = $self->get_rand_ad($array)->clone;
|
||||
}
|
||||
my $ad = $self->get_rand_ad($array)->clone;
|
||||
return {
|
||||
ad => $ad->serialize,
|
||||
continue => 1,
|
||||
current_ad_number => $ad->id,
|
||||
current_ad_number => $self->_get_next_number($current_ad_number),
|
||||
};
|
||||
}
|
||||
|
||||
sub get_rand_ad($self, $array) {
|
||||
my $valid_ads = [ grep { $_->is_active } @$array ];
|
||||
my $max_weight = $self->sum_weights($valid_ads);
|
||||
my $max_weight = $self->sum_weights($array);
|
||||
my $rand = int(rand() * $max_weight);
|
||||
my $sum_weight = 0;
|
||||
for my $ad (@$valid_ads) {
|
||||
|
|
|
@ -21,7 +21,7 @@ sub weight {
|
|||
}
|
||||
|
||||
sub is_active ($self) {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub img {
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package BurguillosInfo::Ads::BurguillosDental;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Moo;
|
||||
|
||||
use parent 'BurguillosInfo::Ad';
|
||||
|
||||
sub id ($self) {
|
||||
return 'burguillos-dental';
|
||||
}
|
||||
|
||||
sub weight {
|
||||
return 50;
|
||||
}
|
||||
|
||||
sub max_alternative {
|
||||
return 3;
|
||||
}
|
||||
|
||||
sub seconds($self) {
|
||||
return 15;
|
||||
}
|
||||
|
||||
sub default_alternative($self) {
|
||||
return int($self->alternative * ($self->max_alternative + 1));
|
||||
}
|
||||
|
||||
sub is_active ($self) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub img ($self) {
|
||||
if ( $self->default_alternative == 2 ) {
|
||||
return '/img/burguillos-dental-ad-0-small.webp'
|
||||
}
|
||||
if ( $self->default_alternative == 1 ) {
|
||||
return '/img/burguillos-dental-ad-1-small.webp'
|
||||
}
|
||||
return '/img/burguillos-dental-ad-1-small.webp'
|
||||
}
|
||||
|
||||
sub text($self) {
|
||||
if ( $self->default_alternative == 2 ) {
|
||||
return 'Pide presupuesto para conseguir una sonrisa perfecta en Burguillos Dental, '.
|
||||
'ubicado en Centro Médico Juan Manuel Pérez Sanchez.';
|
||||
}
|
||||
if ( $self->default_alternative == 1 ) {
|
||||
return '¿Te has hecho ya tu limpieza completa de boca anual? Confia en profesionales, confia en Burguillos Dental, '.
|
||||
'ubicado en Centro Médico Juan Manuel Pérez Sanchez.';
|
||||
}
|
||||
return '¿Te duele un diente? No lo dejes, ven a Burguillos Dental '.
|
||||
'ubicado en Centro Médico Juan Manuel Pérez Sanchez.';
|
||||
}
|
||||
|
||||
sub href {
|
||||
return '/posts/burguillos-dental?come-from-ad=1';
|
||||
}
|
||||
1;
|
|
@ -1,55 +0,0 @@
|
|||
package BurguillosInfo::Ads::ChaletEnVentaCalleHinojo;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use DateTime;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Moo;
|
||||
|
||||
use parent 'BurguillosInfo::Ad';
|
||||
|
||||
sub id ($self) {
|
||||
return 'chalet-en-venta-calle-hinojo';
|
||||
}
|
||||
|
||||
sub weight {
|
||||
return 50;
|
||||
}
|
||||
|
||||
sub max_alternative {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub seconds($self) {
|
||||
return 15;
|
||||
}
|
||||
|
||||
sub default_alternative($self) {
|
||||
return int($self->alternative * ($self->max_alternative + 1));
|
||||
}
|
||||
|
||||
sub is_active ($self) {
|
||||
if (DateTime->new(year => 2024, month => 6, day => 11) < DateTime->now()) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub img ($self) {
|
||||
return '/img/chalet-calle-hinojo.webp';
|
||||
}
|
||||
|
||||
sub text($self) {
|
||||
return 'Chalet pareado en venta en calle Hinojo por 160 000€';
|
||||
}
|
||||
|
||||
sub href {
|
||||
return 'https://www.idealista.com/inmueble/104802645/';
|
||||
}
|
||||
1;
|
|
@ -29,7 +29,7 @@ sub default_alternative($self) {
|
|||
}
|
||||
|
||||
sub is_active ($self) {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub img ($self) {
|
||||
|
|
|
@ -33,7 +33,7 @@ sub default_alternative($self) {
|
|||
}
|
||||
|
||||
sub is_active ($self) {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub img ($self) {
|
||||
|
|
|
@ -29,7 +29,7 @@ sub default_alternative($self) {
|
|||
}
|
||||
|
||||
sub is_active ($self) {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub img ($self) {
|
||||
|
|
|
@ -13,7 +13,6 @@ sub next_ad {
|
|||
my $self = shift;
|
||||
my $ads_factory = BurguillosInfo::Ads->new;
|
||||
my $current_ad_number = $self->param('n');
|
||||
$self->res->headers->access_control_allow_origin('*');
|
||||
$self->render( json => $ads_factory->get_next($current_ad_number) );
|
||||
}
|
||||
1;
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package BurguillosInfo::Controller::Conquer;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||
|
||||
sub index($self) {
|
||||
$self->render;
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,185 @@
|
|||
package BurguillosInfo::Controller::ConquerNode;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||
use UUID::URandom qw/create_uuid_string/;
|
||||
use BurguillosInfo::Schema;
|
||||
|
||||
sub get($self) {
|
||||
my $uuid = $self->param('uuid');
|
||||
my $user = $self->current_user;
|
||||
if (!defined $uuid || !$uuid) {
|
||||
return $self->render(status => 400, json => {
|
||||
error => 'UUID de nodo invalido.',
|
||||
});
|
||||
}
|
||||
my $schema = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||
my @nodes = $schema->search({uuid => $uuid});
|
||||
if (!scalar @nodes) {
|
||||
return $self->render(status => 404, json => {
|
||||
error => 'Nodo no encontrado',
|
||||
});
|
||||
}
|
||||
my $node = $nodes[0];
|
||||
if (defined $user) {
|
||||
return $self->render(json => $node->serialize($user));
|
||||
}
|
||||
return $self->render(json => $node->serialize());
|
||||
}
|
||||
|
||||
sub tryConquer($self) {
|
||||
my $user = $self->current_user;
|
||||
my $schema = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||
if (!defined $user) {
|
||||
return $self->render(status => 401, json => {
|
||||
error => 'You must be logged to conquer a node.',
|
||||
});
|
||||
}
|
||||
if (!defined $user->team) {
|
||||
return $self->render(status => 400, json => {
|
||||
error => 'You must belong to a team to conquer a node.',
|
||||
});
|
||||
}
|
||||
my $uuid = $self->param('uuid');
|
||||
my ($node) = $schema->search({uuid => $uuid});
|
||||
if (!defined $node) {
|
||||
return $self->render(status => 404, json => {
|
||||
error => 'No existe ese nodo.',
|
||||
});
|
||||
}
|
||||
$node->team($user->team);
|
||||
$node->update;
|
||||
$self->render(json => {
|
||||
ok => $JSON::true,
|
||||
});
|
||||
}
|
||||
|
||||
sub create ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'No estás autenticado.',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( !$user->is_admin ) {
|
||||
return $self->render(
|
||||
status => 403,
|
||||
json => {
|
||||
error => 'No tienes permiso para hacer eso.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $input = $self->_expectJson;
|
||||
if ( !defined $input ) {
|
||||
return;
|
||||
}
|
||||
my $name = $input->{name};
|
||||
my $coordinates = $input->{coordinates};
|
||||
my $type = $input->{type};
|
||||
my $description = $input->{description};
|
||||
if ( ref $coordinates ne 'ARRAY' || scalar $coordinates->@* != 2 ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'Formato erroneo de coordenadas.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my ($coordinate_1, $coordinate_2) = $coordinates->@*;
|
||||
if ( !defined $name || length $name < 5 ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error =>
|
||||
'Número incorrecto de carácteres en el nombre del nodo.',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( !defined $description ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'La descripción puede estar vacía, '
|
||||
. 'pero debe existir, si ves este error '
|
||||
. 'desde la aplicación es un error de programación.',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( !defined $type ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'Los nodos deben tener un tipo.'
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( $type ne 'normal' ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'Tipo de nodo no soportado.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $uuid_node = create_uuid_string();
|
||||
my $node;
|
||||
eval {
|
||||
$node = BurguillosInfo::Schema->Schema->resultset('ConquerNode')->new(
|
||||
{
|
||||
uuid => $uuid_node,
|
||||
description => $description,
|
||||
name => $name,
|
||||
type => $type,
|
||||
geometry => \['ST_MakePoint(?, ?)', $coordinate_1, $coordinate_2],
|
||||
}
|
||||
);
|
||||
$node->insert;
|
||||
$node = $node->get_from_storage;
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
return $self->render(
|
||||
status => 500,
|
||||
json => {
|
||||
error => 'El servidor no pudo almacenar el nodo, reporta este error.',
|
||||
}
|
||||
);
|
||||
}
|
||||
return $self->render(
|
||||
status => 200,
|
||||
json => $node->serialize,
|
||||
);
|
||||
}
|
||||
|
||||
sub nearbyNodes($self) {
|
||||
my $user = $self->current_user;
|
||||
if (!defined $user) {
|
||||
return $self->render(status => 401, json => {
|
||||
error => 'No estás loggeado.',
|
||||
});
|
||||
}
|
||||
my @nodes = BurguillosInfo::Schema->Schema->resultset('ConquerNode')->search({});
|
||||
@nodes = map { $_->serialize($user) } @nodes;
|
||||
return $self->render(json => \@nodes);
|
||||
}
|
||||
|
||||
sub _expectJson ($self) {
|
||||
my $input;
|
||||
eval { $input = $self->req->json; };
|
||||
if ($@) {
|
||||
say STDERR $@;
|
||||
$self->_renderError( 400, 'Se esperaba JSON.' );
|
||||
return;
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,180 @@
|
|||
package BurguillosInfo::Controller::ConquerTeam;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||
|
||||
use UUID::URandom qw/create_uuid_string/;
|
||||
use JSON;
|
||||
|
||||
use BurguillosInfo::Schema;
|
||||
|
||||
sub getAll ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'You must be logged to fetch teams.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $uuid = $self->param('uuid');
|
||||
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||
my @teams = $resultset->search({});
|
||||
return $self->render( json => [ map { $_->serialize } @teams ] );
|
||||
}
|
||||
|
||||
sub get ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'You must be logged to fetch a team.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $uuid = $self->param('uuid');
|
||||
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||
my @teams = $resultset->search(
|
||||
{
|
||||
'uuid' => $uuid,
|
||||
}
|
||||
);
|
||||
if ( scalar @teams <= 0 ) {
|
||||
return $self->render(
|
||||
status => 404,
|
||||
json => {
|
||||
error => 'This team does not exist.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $team = $teams[0];
|
||||
return $self->render( json => $team->serialize );
|
||||
}
|
||||
|
||||
sub getSelfTeam ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'You must be logged to fetch your Team.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||
my @teams = $resultset->search(
|
||||
{
|
||||
'players.uuid' => $user->uuid
|
||||
},
|
||||
{
|
||||
join => 'players',
|
||||
}
|
||||
);
|
||||
if ( scalar @teams <= 0 ) {
|
||||
return $self->render( json => undef );
|
||||
}
|
||||
my $team = $teams[0];
|
||||
return $self->render( json => $team );
|
||||
}
|
||||
|
||||
sub _expectJson ($self) {
|
||||
my $input;
|
||||
eval { $input = $self->req->json; };
|
||||
if ($@) {
|
||||
say STDERR $@;
|
||||
$self->_renderError( 400, 'Se esperaba JSON.' );
|
||||
return;
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
|
||||
sub put ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'No estás autenticado.',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( !$user->is_admin ) {
|
||||
return $self->render(
|
||||
status => 403,
|
||||
json => {
|
||||
error => 'No tienes permiso para hacer eso.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $input = $self->_expectJson;
|
||||
if ( !defined $input ) {
|
||||
return;
|
||||
}
|
||||
my $name = $input->{name};
|
||||
my $description = $input->{description};
|
||||
my $color = $input->{color};
|
||||
if ( !defined $name || length $name < 5 ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error =>
|
||||
'Número incorrecto de carácteres en el nombre del equipo..',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( !defined $description ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'La descripción puede estar vacía, '
|
||||
. 'pero debe existir, si ves este error '
|
||||
. 'desde la aplicación es un error de programación.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $color_regex_char = qr/[0-9a-fA-F]/;
|
||||
if ( !defined $color || $color !~ /^#(?:${color_regex_char}{6}|${color_regex_char}{3})$/ ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'Formato de color invalido',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $uuid_team = create_uuid_string();
|
||||
my $team;
|
||||
eval {
|
||||
$team = BurguillosInfo::Schema->Schema->resultset('ConquerTeam')->new(
|
||||
{
|
||||
uuid => $uuid_team,
|
||||
description => $description,
|
||||
name => $name,
|
||||
color => $color,
|
||||
}
|
||||
);
|
||||
$team->insert;
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
return $self->render(
|
||||
status => 500,
|
||||
json => {
|
||||
error =>
|
||||
'El servidor no pudo almacenar el equipo, reporta este error.',
|
||||
}
|
||||
);
|
||||
}
|
||||
return $self->render(
|
||||
status => 200,
|
||||
json => $team->serialize,
|
||||
);
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,70 @@
|
|||
package BurguillosInfo::Controller::ConquerTile;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||
use Path::Tiny;
|
||||
use Mojo::UserAgent;
|
||||
use DateTime::Format::HTTP;
|
||||
use DateTime;
|
||||
|
||||
my $cache_files_dir =
|
||||
path(__FILE__)->parent->parent->parent->parent->child('cache/tiles/');
|
||||
|
||||
sub _cache_response ($self) {
|
||||
my $tomorrow_same_hour_datetime = DateTime->now->add( days => 1 );
|
||||
$self->res->headers->cache_control("max_age=@{[3600*24]}");
|
||||
$self->res->headers->expires(
|
||||
DateTime::Format::HTTP->format_datetime($tomorrow_same_hour_datetime) );
|
||||
}
|
||||
|
||||
sub tile ($self) {
|
||||
my $zoom = $self->stash('zoom');
|
||||
my $x = $self->stash('x');
|
||||
my $y = $self->stash('y');
|
||||
my $candidate_file = $cache_files_dir->child("$zoom-$x-$y.png");
|
||||
if ( -f $candidate_file ) {
|
||||
$self->_cache_response;
|
||||
return $self->_render_png($candidate_file);
|
||||
}
|
||||
if ( !defined $self->current_user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
text => '¡¡No estás loggeado, no puedes cargar mapa nuevo.!!'
|
||||
);
|
||||
}
|
||||
$self->_cache_response;
|
||||
my $file_to_write = $candidate_file;
|
||||
my $ua = Mojo::UserAgent->new;
|
||||
my $png_tile =
|
||||
$ua->get("https://tile.openstreetmap.org/$zoom/$x/$y.png")->result->body;
|
||||
open my $fh, '|-', 'convert', '/dev/stdin', '-channel', 'RGB', '-negate',
|
||||
$file_to_write;
|
||||
print $fh $png_tile;
|
||||
close $fh;
|
||||
$self->_render_png($file_to_write);
|
||||
$self->_delete_extra_files();
|
||||
}
|
||||
|
||||
sub _delete_extra_files ($self) {
|
||||
my @files = $cache_files_dir->children;
|
||||
if ( scalar @files < 20001 ) {
|
||||
return;
|
||||
}
|
||||
@files = sort { -M $a <=> -M $b } @files;
|
||||
for ( my $i = 0 ; $i < ( scalar @files ) - 20000 ; $i++ ) {
|
||||
system 'rm', '-v', $files[$i];
|
||||
}
|
||||
}
|
||||
|
||||
sub _render_png ( $self, $file ) {
|
||||
system 'touch', $file;
|
||||
return $self->render( data => $file->slurp_raw, status => 200,
|
||||
format => 'png' );
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,103 @@
|
|||
package BurguillosInfo::Controller::ConquerUserCurrentEnemy;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||
|
||||
use UUID::URandom qw/create_uuid_string/;
|
||||
use JSON;
|
||||
|
||||
use BurguillosInfo::Schema;
|
||||
use BurguillosInfo::Species;
|
||||
|
||||
sub listEnemiesGlobal ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'Debes estar autenticado.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my $current_enemies = $self->_get_enemies($user);
|
||||
if ( scalar @$current_enemies ) {
|
||||
return $self->_return_enemies($current_enemies);
|
||||
}
|
||||
$self->_generate_enemies_global($user);
|
||||
$current_enemies = $self->_get_enemies($user);
|
||||
return $self->_return_enemies($current_enemies);
|
||||
}
|
||||
|
||||
sub _return_enemies ( $self, $current_enemies ) {
|
||||
return $self->render( json => [ map { $_->serialize } @$current_enemies ] );
|
||||
}
|
||||
|
||||
sub _get_enemies ( $self, $user ) {
|
||||
my $resultset_current_enemies =
|
||||
BurguillosInfo::Schema->Schema->resultset('ConquerUserCurrentEnemy');
|
||||
my @current_enemies = $resultset_current_enemies->search(
|
||||
{
|
||||
'user_object.uuid' => $user->uuid,
|
||||
},
|
||||
{
|
||||
join => 'user_object',
|
||||
}
|
||||
);
|
||||
return \@current_enemies;
|
||||
}
|
||||
|
||||
sub _generate_enemies_global ( $self, $user ) {
|
||||
my $minimum_number_enemies = 2;
|
||||
my $maximum_number_enemies = 6;
|
||||
my $number_enemies =
|
||||
$self->_calculate_number_of_enemies( $minimum_number_enemies,
|
||||
$maximum_number_enemies );
|
||||
for ( my $i = 0 ; $i < $number_enemies ; $i++ ) {
|
||||
$self->_generate_enemy_global($user);
|
||||
}
|
||||
}
|
||||
|
||||
sub _generate_enemy_global ( $self, $user ) {
|
||||
my $resultset_current_enemies =
|
||||
BurguillosInfo::Schema->Schema->resultset('ConquerUserCurrentEnemy');
|
||||
my $uuid = create_uuid_string();
|
||||
my $species = BurguillosInfo::Species->new;
|
||||
my @species = @{ $species->list_can_be_global };
|
||||
my $selected_species = $species[int( rand( scalar @species ) )];
|
||||
my $enemy = $resultset_current_enemies->new(
|
||||
{
|
||||
uuid => $uuid,
|
||||
species => $selected_species->id,
|
||||
level => $self->_get_level_enemy($user),
|
||||
user => $user->uuid,
|
||||
}
|
||||
);
|
||||
$enemy->insert;
|
||||
}
|
||||
|
||||
sub _get_level_enemy ( $self, $user ) {
|
||||
my $max_enemy_level = int( $user->level / 2 ) + 1;
|
||||
if ( $max_enemy_level < $user->level - 10 ) {
|
||||
$max_enemy_level = $user->level - 10;
|
||||
}
|
||||
if ( $max_enemy_level < 3 ) {
|
||||
$max_enemy_level = 3;
|
||||
}
|
||||
my $min_enemy_level = $max_enemy_level - 5;
|
||||
if ( $min_enemy_level < 2 ) {
|
||||
$min_enemy_level = 2;
|
||||
}
|
||||
return $min_enemy_level +
|
||||
int( rand( $max_enemy_level - $min_enemy_level + 1 ) );
|
||||
}
|
||||
|
||||
sub _calculate_number_of_enemies ( $self, $min, $max ) {
|
||||
return $min + int( rand( $max - $min + 1 ) );
|
||||
}
|
||||
1;
|
|
@ -89,8 +89,6 @@ sub submit_login {
|
|||
$self->render( text => 'Server error.', status => 500 );
|
||||
return;
|
||||
}
|
||||
say $password;
|
||||
say $bcrypted_pass;
|
||||
if ( !bcrypt_check( $password, $bcrypted_pass ) ) {
|
||||
$self->render( text => 'Wrong password', status => 401 );
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
package BurguillosInfo::Controller::UserConquer;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||
|
||||
use UUID::URandom qw/create_uuid_string/;
|
||||
use Crypt::Bcrypt qw/bcrypt bcrypt_check/;
|
||||
use Crypt::URandom qw/urandom/;
|
||||
use JSON;
|
||||
|
||||
use BurguillosInfo::Schema;
|
||||
|
||||
my $username_minimum_chars = 3;
|
||||
my $username_maximum_chars = 15;
|
||||
my $password_minimum_chars = 8;
|
||||
my $password_maximum_chars = 4096;
|
||||
|
||||
sub setTeamForUser($self) {
|
||||
my $user = $self->current_user;
|
||||
if (!defined $user) {
|
||||
return $self->_renderError(401, 'No estás loggeado.');
|
||||
}
|
||||
my $input = $self->_expectJson;
|
||||
if (!defined $input) {
|
||||
return;
|
||||
}
|
||||
my $node_uuid = $input->{node};
|
||||
my $team_uuid = $input->{team};
|
||||
my $resultset_team = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||
my $resultset_node = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||
my @teams = $resultset_team->search({uuid => $team_uuid});
|
||||
my @nodes = $resultset_node->search({uuid => $node_uuid});
|
||||
if (scalar @teams < 1) {
|
||||
return $self->render(status => 404, json => {
|
||||
error => 'No se encontró ese equipo.',
|
||||
});
|
||||
}
|
||||
if (scalar @nodes < 1) {
|
||||
return $self->render(status => 404, json => {
|
||||
error => 'No se encontró este nodo.',
|
||||
});
|
||||
}
|
||||
my $team = $teams[0];
|
||||
my $node = $nodes[0];
|
||||
if (!$node->is_near($user)) {
|
||||
return $self->render(status => 400, json => {
|
||||
error => 'Estás demasiado lejos del nodo.',
|
||||
});
|
||||
}
|
||||
$user = $user->get_from_storage;
|
||||
$user->team_object($team);
|
||||
$user->update;
|
||||
return $self->render(json => {
|
||||
ok => $JSON::true,
|
||||
});
|
||||
}
|
||||
|
||||
sub get_self ($self) {
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->_renderError( 401, 'No estás loggeado.' );
|
||||
}
|
||||
return $self->render( json => $user->serialize_to_owner, status => 200 );
|
||||
}
|
||||
|
||||
sub create ($self) {
|
||||
my $input = $self->_expectJson;
|
||||
if ( !defined $input ) {
|
||||
return;
|
||||
}
|
||||
my $username = $input->{username};
|
||||
my $password = $input->{password};
|
||||
my $repeat_password = $input->{repeat_password};
|
||||
return
|
||||
unless $self->_createCheckInput( $username, $password, $repeat_password );
|
||||
return $self->_createUser( $username, $password );
|
||||
}
|
||||
|
||||
sub _expectJson ($self) {
|
||||
my $input;
|
||||
eval { $input = $self->req->json; };
|
||||
if ($@) {
|
||||
say STDERR $@;
|
||||
$self->_renderError( 400, 'Se esperaba JSON.' );
|
||||
return;
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
|
||||
sub login ($self) {
|
||||
my $input = $self->_expectJson;
|
||||
if ( !defined $input ) {
|
||||
return;
|
||||
}
|
||||
my $username = $input->{username};
|
||||
my $password = $input->{password};
|
||||
|
||||
my $resultset_conquer_user =
|
||||
BurguillosInfo::Schema->Schema->resultset('ConquerUser');
|
||||
my @tentative_users =
|
||||
$resultset_conquer_user->search( { username => $username } );
|
||||
my $tentative_user = $tentative_users[0];
|
||||
if ( !defined $tentative_user ) {
|
||||
$self->_renderError( 401, 'El usuario especificado no existe.' );
|
||||
return;
|
||||
}
|
||||
if ( !bcrypt_check( $password, $tentative_user->encrypted_password ) ) {
|
||||
$self->_renderError( 401, 'Contraseña incorrecta.' );
|
||||
return;
|
||||
}
|
||||
my $user = $tentative_user;
|
||||
$self->set_current_user($user);
|
||||
$self->render(
|
||||
json => {
|
||||
success => $JSON::true
|
||||
},
|
||||
status => 200
|
||||
);
|
||||
}
|
||||
|
||||
sub setCoordinates ($self) {
|
||||
my $input = $self->_expectJson;
|
||||
my $user = $self->current_user;
|
||||
if ( !defined $user ) {
|
||||
return $self->render(
|
||||
status => 401,
|
||||
json => {
|
||||
error => 'Debes estar loggeado para cambiar tus'
|
||||
. ' coordenadas.',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( !defined $input ) {
|
||||
return;
|
||||
}
|
||||
if ( ref $input ne 'ARRAY' && scalar $input->@* == 2 ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'Mal formato de coordenadas, debe ser '
|
||||
. 'un array de exactamente 2 números reales.',
|
||||
}
|
||||
);
|
||||
}
|
||||
$user->coordinates($input);
|
||||
$user->update;
|
||||
return $self->render(
|
||||
status => 200,
|
||||
json => {
|
||||
ok => $JSON::true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub _createUser ( $self, $username, $password ) {
|
||||
my $user;
|
||||
my $uuid = create_uuid_string();
|
||||
my $new_salt = urandom(16);
|
||||
my $encrypted_password = bcrypt $password, '2b', 12, $new_salt;
|
||||
eval {
|
||||
$user = BurguillosInfo::Schema->Schema->resultset('ConquerUser')->new(
|
||||
{
|
||||
uuid => $uuid,
|
||||
encrypted_password => $encrypted_password,
|
||||
username => $username
|
||||
}
|
||||
);
|
||||
$user->coordinates( [ 0, 0 ] );
|
||||
$user->insert;
|
||||
};
|
||||
if ($@) {
|
||||
if ( $@ =~ /Key \((.*?)\)=\((.*?)\) already exists\./ ) {
|
||||
return $self->_renderError( 400,
|
||||
"La clave $1 ($2) ya existe en la base de datos.",
|
||||
);
|
||||
}
|
||||
say STDERR $@;
|
||||
return $self->_renderError( 400,
|
||||
'No se pudo crear el usuario por razones desconocidas.' );
|
||||
}
|
||||
$self->render( status => 200, json => $user->serialize_to_owner );
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _renderError ( $self, $status, $message ) {
|
||||
$self->render( status => $status, json => { error => $message } );
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _createCheckInput ( $self, $username, $password, $repeat_password ) {
|
||||
if ( !defined $username
|
||||
|| $username !~
|
||||
/^(?:\w|\d|[ÑÁÉÍÓÚñáéíóú ]){$username_minimum_chars,$username_maximum_chars}$/
|
||||
)
|
||||
{
|
||||
return $self->_renderError( 400,
|
||||
"Username invalido, las reglas son tamaño entre $username_minimum_chars y $username_maximum_chars"
|
||||
. ' carácteres y solo se podrán usar letras, números y espacios.'
|
||||
);
|
||||
}
|
||||
if ( !defined $password
|
||||
|| $password eq $username
|
||||
|| $password !~ /^.{$password_minimum_chars,$password_maximum_chars}$/
|
||||
|| $password =~ /^\d+$/ )
|
||||
{
|
||||
return $self->_renderError(
|
||||
400,
|
||||
'Contraseña invalida, las reglas son la contraseña debe ser'
|
||||
. ' distinta al nombre de usuario, la contraseña debe tener entre'
|
||||
. " $password_minimum_chars y $password_maximum_chars carácteres"
|
||||
. ' (Tu contraseña no se guardará en texto plano, el límite de'
|
||||
. " $password_maximum_chars caracteres es para evitar denegaciones"
|
||||
. ' de servicio), la contraseña no puede estar compuesta solo de números.',
|
||||
);
|
||||
}
|
||||
if ( !defined $repeat_password || $password ne $repeat_password ) {
|
||||
$self->_renderError(
|
||||
400,
|
||||
'El campo de repetir contraseña debe coincidir de forma'
|
||||
. ' totalmente exacta con el campo de contraseña para asegurar'
|
||||
. ' que podrás recordar la contraseña y/o que no has cometido'
|
||||
. ' ningún error, si pierdes el acceso a tu cuenta no podrás'
|
||||
. ' recuperarlo de ningún modo.',
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
1;
|
|
@ -28,11 +28,11 @@ sub MIGRATIONS {
|
|||
path TEXT,
|
||||
FOREIGN KEY (path) REFERENCES paths(path)
|
||||
)',
|
||||
'ALTER TABLE paths ADD column last_seen TIMESTAMP;',
|
||||
'ALTER TABLE paths ADD COLUMN last_seen TIMESTAMP;',
|
||||
'ALTER TABLE paths ALTER COLUMN last_seen SET DEFAULT NOW();',
|
||||
'ALTER TABLE requests ADD PRIMARY KEY (uuid)',
|
||||
'CREATE INDEX request_extra_index on requests (date, path);',
|
||||
'ALTER TABLE requests ADD column referer text;',
|
||||
'ALTER TABLE requests ADD COLUMN referer text;',
|
||||
'CREATE INDEX request_referer_index on requests (referer);',
|
||||
'ALTER TABLE requests ADD COLUMN country TEXT;',
|
||||
'CREATE INDEX request_country_index on requests (country);',
|
||||
|
@ -49,18 +49,69 @@ sub MIGRATIONS {
|
|||
id_farmacia TEXT NOT NULL
|
||||
);',
|
||||
'CREATE INDEX farmacia_guardia_index on farmacia_guardia (date, id_farmacia, uuid);',
|
||||
'CREATE TABLE conquer_user (
|
||||
uuid UUID NOT NULL PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
encrypted_password TEXT NOT NULL,
|
||||
last_activity TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
is_admin BOOLEAN NOT NULL DEFAULT false,
|
||||
registration_date TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);',
|
||||
'CREATE TABLE conquer_node (
|
||||
uuid UUID NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
coordinate_1 REAL NOT NULL,
|
||||
coordinate_2 REAL NOT NULL,
|
||||
description TEXT NOT NULL
|
||||
);',
|
||||
'CREATE INDEX index_conquer_node_coordinate_1 on conquer_node (coordinate_1);',
|
||||
'CREATE INDEX index_conquer_node_coordinate_2 on conquer_node (coordinate_2);',
|
||||
'ALTER TABLE conquer_user ADD COLUMN last_coordinate_1 REAL NOT NULL DEFAULT 0;',
|
||||
'ALTER TABLE conquer_user ALTER COLUMN last_coordinate_1 DROP DEFAULT;',
|
||||
'ALTER TABLE conquer_user ADD COLUMN last_coordinate_2 REAL NOT NULL DEFAULT 0;',
|
||||
'ALTER TABLE conquer_user ALTER COLUMN last_coordinate_2 DROP DEFAULT;',
|
||||
'CREATE TABLE conquer_teams (
|
||||
uuid UUID NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT \'\',
|
||||
points INTEGER NOT NULL DEFAULT 0
|
||||
);',
|
||||
'ALTER TABLE conquer_user ADD COLUMN team UUID REFERENCES conquer_teams (uuid);',
|
||||
'ALTER TABLE conquer_node ADD COLUMN team UUID REFERENCES conquer_teams (uuid);',
|
||||
'ALTER TABLE conquer_teams ADD COLUMN color TEXT NOT NULL DEFAULT \'#000\';',
|
||||
'ALTER TABLE conquer_teams ALTER COLUMN color SET DEFAULT \'#555\';',
|
||||
'ALTER TABLE conquer_teams ALTER COLUMN color SET DEFAULT \'#aaa\';',
|
||||
'CREATE EXTENSION IF NOT EXISTS postgis;',
|
||||
'ALTER TABLE conquer_node ADD COLUMN geometry GEOMETRY NULL;',
|
||||
'UPDATE conquer_node SET geometry=ST_MakePoint(coordinate_1, coordinate_2);',
|
||||
'ALTER TABLE conquer_node ALTER COLUMN geometry SET NOT NULL;',
|
||||
'ALTER TABLE conquer_node DROP COLUMN coordinate_1;',
|
||||
'ALTER TABLE conquer_node DROP COLUMN coordinate_2;',
|
||||
'ALTER TABLE conquer_user ADD COLUMN experience INTEGER NOT NULL DEFAULT 125;',
|
||||
'ALTER TABLE conquer_user ADD COLUMN current_hp INTEGER NOT NULL DEFAULT 999;',
|
||||
'CREATE TABLE conquer_user_current_enemy (
|
||||
uuid UUID NOT NULL PRIMARY KEY,
|
||||
"user" UUID NOT NULL REFERENCES conquer_user(uuid),
|
||||
species INTEGER NOT NULL,
|
||||
is_battled BOOLEAN DEFAULT false,
|
||||
is_selected_to_battle BOOLEAN DEFAULT false,
|
||||
level INTEGER NOT NULL
|
||||
);',
|
||||
'ALTER TABLE conquer_user_current_enemy ALTER COLUMN species TYPE TEXT;',
|
||||
);
|
||||
}
|
||||
|
||||
sub _populate_locations ($dbh) {
|
||||
require BurguillosInfo;
|
||||
require BurguillosInfo::Tracking;
|
||||
my $tracking = BurguillosInfo::Tracking->new( BurguillosInfo->new );
|
||||
my $page = 0;
|
||||
while (1) {
|
||||
last if !_update_request_page( $dbh, $tracking, $page );
|
||||
$page += 100;
|
||||
}
|
||||
# This subroutine crashes the migrations.
|
||||
# require BurguillosInfo;
|
||||
# require BurguillosInfo::Tracking;
|
||||
# my $tracking = BurguillosInfo::Tracking->new( BurguillosInfo->new );
|
||||
# my $page = 0;
|
||||
# while (1) {
|
||||
# last if !_update_request_page( $dbh, $tracking, $page );
|
||||
# $page += 100;
|
||||
# }
|
||||
}
|
||||
|
||||
sub _update_request_page ( $dbh, $tracking, $page ) {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package BurguillosInfo::Schema;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
our $VERSION = 1;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use BurguillosInfo;
|
||||
|
||||
use parent 'DBIx::Class::Schema';
|
||||
|
||||
__PACKAGE__->load_namespaces();
|
||||
|
||||
my $schema;
|
||||
|
||||
sub Schema ($class) {
|
||||
if ( !defined $schema ) {
|
||||
use BurguillosInfo::DB;
|
||||
BurguillosInfo::DB->connect;
|
||||
my $app = BurguillosInfo->new;
|
||||
my $config = $app->{config};
|
||||
my $database_config = $config->{db};
|
||||
my $dbname = $database_config->{database};
|
||||
my $host = $database_config->{host};
|
||||
my $port = $database_config->{port};
|
||||
my $user = $database_config->{user};
|
||||
my $password = $database_config->{password};
|
||||
my $dsn = 'dbi:Pg:';
|
||||
|
||||
if ( !defined $dbname ) {
|
||||
die "The key database/dbname must be configured.";
|
||||
}
|
||||
$dsn .= "dbname=$dbname";
|
||||
if ( defined $host ) {
|
||||
$dsn .= ";host=$host";
|
||||
}
|
||||
if ( defined $port ) {
|
||||
$dsn .= ";port=$port";
|
||||
}
|
||||
|
||||
# Undef is perfectly fine for username and password.
|
||||
$schema = $class->connect(
|
||||
$dsn, $user,
|
||||
$password,
|
||||
{
|
||||
auto_savepoint => 1,
|
||||
Callbacks => {
|
||||
connected => sub {
|
||||
shift->do('set timezone = UTC');
|
||||
return;
|
||||
}
|
||||
},
|
||||
quote_char => '"',
|
||||
}
|
||||
);
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,121 @@
|
|||
package BurguillosInfo::Schema::Result::ConquerNode;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use JSON;
|
||||
use GIS::Distance;
|
||||
|
||||
__PACKAGE__->table('conquer_node');
|
||||
__PACKAGE__->load_components("TimeStamp");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
uuid => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 0,
|
||||
},
|
||||
name => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
default_value => \'0',
|
||||
},
|
||||
type => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
description => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
geometry => {
|
||||
data_type => 'geometry',
|
||||
is_nullable => 0,
|
||||
},
|
||||
team => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 1,
|
||||
},
|
||||
);
|
||||
|
||||
sub coordinate_2 ($self) {
|
||||
require BurguillosInfo::Schema;
|
||||
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||
my ($new_self) = $resultset->search(
|
||||
{ uuid => $self->uuid },
|
||||
{
|
||||
'+select' => {
|
||||
ST_Y => { ST_Centroid => 'geometry' },
|
||||
-as => 'coordinate_2',
|
||||
}
|
||||
}
|
||||
);
|
||||
return $new_self->get_column('coordinate_2');
|
||||
}
|
||||
|
||||
sub coordinate_1 ($self) {
|
||||
require BurguillosInfo::Schema;
|
||||
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||
my ($new_self) = $resultset->search(
|
||||
{ uuid => $self->uuid },
|
||||
{
|
||||
'+select' => {
|
||||
ST_X => { ST_Centroid => 'geometry' },
|
||||
-as => 'coordinate_1',
|
||||
}
|
||||
}
|
||||
);
|
||||
return $new_self->get_column('coordinate_1');
|
||||
}
|
||||
|
||||
sub serialize ( $self, $player = undef ) {
|
||||
$self = $self->get_from_storage();
|
||||
my $return = {
|
||||
kind => 'ConquerNode',
|
||||
uuid => $self->uuid,
|
||||
name => $self->name,
|
||||
description => $self->description,
|
||||
type => $self->type,
|
||||
coordinate_1 => $self->coordinate_1,
|
||||
coordinate_2 => $self->coordinate_2,
|
||||
is_near => $self->is_near($player),
|
||||
team => $self->team,
|
||||
};
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub is_near ( $self, $player ) {
|
||||
if ( !defined $player ) {
|
||||
return $JSON::false;
|
||||
}
|
||||
|
||||
# Meters
|
||||
if ( $self->get_distance_to_player($player) < 100 ) {
|
||||
return $JSON::true;
|
||||
}
|
||||
return $JSON::false;
|
||||
}
|
||||
|
||||
sub get_distance_to_player ( $self, $player ) {
|
||||
my $longitude_player = $player->last_coordinate_1;
|
||||
my $latitude_player = $player->last_coordinate_2;
|
||||
my $longitude_node = $self->coordinate_1;
|
||||
my $latitude_node = $self->coordinate_2;
|
||||
my $gis = GIS::Distance->new;
|
||||
|
||||
# Setting distance to meters.
|
||||
my $distance = $gis->distance_metal(
|
||||
$latitude_node, $longitude_node,
|
||||
$latitude_player, $longitude_player
|
||||
) * 1000;
|
||||
}
|
||||
|
||||
__PACKAGE__->set_primary_key('uuid');
|
||||
__PACKAGE__->belongs_to( 'team_object',
|
||||
'BurguillosInfo::Schema::Result::ConquerTeam', 'team' );
|
||||
1;
|
|
@ -0,0 +1,55 @@
|
|||
package BurguillosInfo::Schema::Result::ConquerTeam;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use JSON;
|
||||
|
||||
__PACKAGE__->table('conquer_teams');
|
||||
__PACKAGE__->load_components("TimeStamp");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
uuid => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 0,
|
||||
},
|
||||
name => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
description => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
points => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
},
|
||||
color => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key('uuid');
|
||||
__PACKAGE__->has_many( players => 'BurguillosInfo::Schema::Result::ConquerUser', 'team');
|
||||
__PACKAGE__->has_many( nodes => 'BurguillosInfo::Schema::Result::ConquerNode', 'team');
|
||||
|
||||
sub serialize ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
return {
|
||||
kind => 'ConquerTeam',
|
||||
uuid => $self->uuid,
|
||||
name => $self->name,
|
||||
description => $self->description,
|
||||
points => $self->points,
|
||||
color => $self->color,
|
||||
};
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,153 @@
|
|||
package BurguillosInfo::Schema::Result::ConquerUser;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use JSON;
|
||||
|
||||
__PACKAGE__->table('conquer_user');
|
||||
__PACKAGE__->load_components("TimeStamp");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
uuid => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 0,
|
||||
},
|
||||
team => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 1,
|
||||
},
|
||||
username => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
encrypted_password => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
last_activity => {
|
||||
data_type => 'timestamp',
|
||||
is_nullable => 0,
|
||||
default_value => \'NOW()',
|
||||
},
|
||||
registration_date => {
|
||||
data_type => 'timestamp',
|
||||
is_nullable => 0,
|
||||
default_value => \'NOW()',
|
||||
},
|
||||
is_admin => {
|
||||
data_type => 'boolean',
|
||||
is_nullable => 0,
|
||||
default_value => \'0',
|
||||
},
|
||||
last_coordinate_1 => {
|
||||
data_type => 'real',
|
||||
is_nullable => 0,
|
||||
default_value => \'0',
|
||||
},
|
||||
last_coordinate_2 => {
|
||||
data_type => 'real',
|
||||
is_nullable => 0,
|
||||
default_value => \'0',
|
||||
},
|
||||
experience => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
default_value => \'125',
|
||||
},
|
||||
current_hp => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
default_value => \'999',
|
||||
}
|
||||
);
|
||||
|
||||
sub max_health($self) {
|
||||
$self = $self->get_from_storage();
|
||||
my $base = 50;
|
||||
my $born_value = 31;
|
||||
return int(
|
||||
(($base * 2 + $born_value) * $self->level)
|
||||
/ 100 + $self->level + 10
|
||||
);
|
||||
}
|
||||
|
||||
sub level($self) {
|
||||
$self = $self->get_from_storage();
|
||||
return int($self->experience ** (1/3) + 0.0000000000001);
|
||||
}
|
||||
|
||||
sub attack($self) {
|
||||
$self = $self->get_from_storage();
|
||||
my $base = 50;
|
||||
my $born_value = 31;
|
||||
return int(
|
||||
(($base * 2 + $self->level)*$self->level)
|
||||
/100
|
||||
);
|
||||
}
|
||||
|
||||
sub defense($self) {
|
||||
$self = $self->get_from_storage();
|
||||
my $base = 50;
|
||||
my $born_value = 31;
|
||||
return int(
|
||||
(($base * 2 + $self->level)*$self->level)
|
||||
/100
|
||||
);
|
||||
}
|
||||
|
||||
sub health($self, $health = undef) {
|
||||
$self = $self->get_from_storage();
|
||||
my $hp = $self->current_hp;
|
||||
if ($hp > $self->max_health) {
|
||||
$self->current_hp($self->max_health);
|
||||
$self->update;
|
||||
$self = $self->get_from_storage();
|
||||
}
|
||||
if (defined $health) {
|
||||
if ($health > $self->max_health) {
|
||||
$health = $self->max_health;
|
||||
}
|
||||
$self->current_hp($health);
|
||||
$self->update;
|
||||
$self = $self->get_from_storage();
|
||||
}
|
||||
return $self->current_hp;
|
||||
}
|
||||
|
||||
sub coordinates ( $self, $coordinates = undef ) {
|
||||
if ( defined $coordinates ) {
|
||||
if ( ref $coordinates ne 'ARRAY' || scalar $coordinates->@* != 2 ) {
|
||||
die 'The second parameter of this subroutine '
|
||||
. 'must be an ARRAYREF of exactly two elements.';
|
||||
}
|
||||
$self->last_coordinate_1( $coordinates->[0] );
|
||||
$self->last_coordinate_2( $coordinates->[1] );
|
||||
}
|
||||
return [ $self->last_coordinate_1, $self->last_coordinate_2 ];
|
||||
}
|
||||
|
||||
sub serialize_to_owner ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
return {
|
||||
kind => 'ConquerUser',
|
||||
uuid => $self->uuid,
|
||||
team => $self->team,
|
||||
username => $self->username,
|
||||
is_admin => $self->is_admin ? $JSON::true : $JSON::false,
|
||||
last_activity => $self->last_activity,
|
||||
registration_date => $self->registration_date,
|
||||
};
|
||||
}
|
||||
__PACKAGE__->set_primary_key('uuid');
|
||||
__PACKAGE__->belongs_to('team_object', 'BurguillosInfo::Schema::Result::ConquerTeam', 'team');
|
||||
__PACKAGE__->add_unique_constraint( "unique_constraint_username",
|
||||
['username'] );
|
||||
1;
|
|
@ -0,0 +1,101 @@
|
|||
package BurguillosInfo::Schema::Result::ConquerUserCurrentEnemy;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use JSON;
|
||||
use BurguillosInfo::Species;
|
||||
|
||||
__PACKAGE__->table('conquer_user_current_enemy');
|
||||
__PACKAGE__->load_components("TimeStamp");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
uuid => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 0,
|
||||
},
|
||||
user => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 0,
|
||||
},
|
||||
species => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
is_selected_to_battle => {
|
||||
data_type => 'boolean',
|
||||
is_nullable => 0,
|
||||
default_value => \'false',
|
||||
},
|
||||
is_battled => {
|
||||
data_type => 'boolean',
|
||||
is_nullable => 0,
|
||||
default_value => \'false',
|
||||
},
|
||||
level => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
sub serialize ($self) {
|
||||
my $species = BurguillosInfo::Species->new;
|
||||
my $specie = $species->get( $self->species );
|
||||
return {
|
||||
uuid => $self->uuid,
|
||||
species => $specie->serialize,
|
||||
level => $self->level,
|
||||
max_health => $self->max_health,
|
||||
};
|
||||
}
|
||||
|
||||
sub max_health ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
my $base = 50;
|
||||
my $born_value = 31;
|
||||
return
|
||||
int( ( ( $base * 2 + $born_value ) * $self->level ) / 100 +
|
||||
$self->level +
|
||||
10 );
|
||||
}
|
||||
|
||||
sub experience_drop ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
return int( $self->level / 7 * 179 );
|
||||
}
|
||||
|
||||
sub experience ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
return int( $self->level**(3) );
|
||||
}
|
||||
|
||||
sub attack ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
my $base = 50;
|
||||
my $born_value = 31;
|
||||
return int( ( ( $base * 2 + $self->level ) * $self->level ) / 100 );
|
||||
}
|
||||
|
||||
sub defense ($self) {
|
||||
$self = $self->get_from_storage();
|
||||
my $base = 50;
|
||||
my $born_value = 31;
|
||||
return int( ( ( $base * 2 + $self->level ) * $self->level ) / 100 );
|
||||
}
|
||||
|
||||
sub health ($self) {
|
||||
|
||||
# Combat result is decided from the start of battle.
|
||||
return $self->max_health;
|
||||
}
|
||||
|
||||
__PACKAGE__->set_primary_key('uuid');
|
||||
__PACKAGE__->belongs_to( 'user_object',
|
||||
'BurguillosInfo::Schema::Result::ConquerUser', 'user' );
|
||||
1;
|
|
@ -0,0 +1,24 @@
|
|||
package BurguillosInfo::Specie;
|
||||
|
||||
use v5.36.0;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Moo::Role;
|
||||
|
||||
sub can_be_global {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub serialize ($self) {
|
||||
return {
|
||||
id => $self->id,
|
||||
name => $self->name,
|
||||
image => $self->image,
|
||||
};
|
||||
}
|
||||
|
||||
requires 'id name image';
|
||||
1;
|
|
@ -0,0 +1,58 @@
|
|||
package BurguillosInfo::Species;
|
||||
|
||||
use v5.36.0;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Moo;
|
||||
|
||||
use Module::Pluggable
|
||||
search_path => ['BurguillosInfo::Species'],
|
||||
instantiate => 'new',
|
||||
on_require_error => sub ( $plugin, $error ) {
|
||||
die $error;
|
||||
};
|
||||
|
||||
{
|
||||
my %hash_species;
|
||||
|
||||
sub _hash ($self) {
|
||||
if ( !scalar keys %hash_species ) {
|
||||
$self->_populate_hash;
|
||||
}
|
||||
return {%hash_species};
|
||||
}
|
||||
|
||||
sub _populate_hash ($self) {
|
||||
my @species = $self->plugins();
|
||||
print Data::Dumper::Dumper \@species;
|
||||
for my $specie (@species) {
|
||||
$self->_check_specie_valid($specie);
|
||||
if (exists $hash_species{$specie->id}) {
|
||||
die "Duplicated species id @{[$specie->id]}.";
|
||||
}
|
||||
$hash_species{$specie->id} = $specie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _check_specie_valid ( $self, $specie ) {
|
||||
if ( !$specie->does('BurguillosInfo::Specie') ) {
|
||||
die "$specie does not implement BurguillosInfo::Specie.";
|
||||
}
|
||||
}
|
||||
|
||||
sub get($self, $id) {
|
||||
return $self->_hash->{$id};
|
||||
}
|
||||
|
||||
sub list($self) {
|
||||
my @species_keys = keys %{$self->_hash};
|
||||
my $species = [ sort { $a->id cmp $b->id } map { $self->_hash->{$_} } @species_keys ];
|
||||
return $species;
|
||||
}
|
||||
|
||||
sub list_can_be_global($self) {
|
||||
return [ grep { $_->can_be_global } $self->list->@* ];
|
||||
}
|
||||
1;
|
|
@ -0,0 +1,28 @@
|
|||
package BurguillosInfo::Species::Murcielago;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
||||
use Moo;
|
||||
|
||||
use parent 'BurguillosInfo::Specie';
|
||||
|
||||
sub id {
|
||||
return 'murcielago';
|
||||
}
|
||||
|
||||
sub name {
|
||||
return 'Murcielago';
|
||||
}
|
||||
|
||||
sub image {
|
||||
return '/img/conquer/species/murcielago.png';
|
||||
}
|
||||
|
||||
sub can_be_global {
|
||||
return 1;
|
||||
}
|
||||
1;
|
|
@ -25,7 +25,10 @@
|
|||
"dependencies": {
|
||||
"babel-loader": "^9.1.3",
|
||||
"ol": "^8.1.0",
|
||||
"protoc-gen-js": "^3.21.2",
|
||||
"tablesort": "^5.3.0",
|
||||
"ts-loader": "^9.5.0"
|
||||
"ts-loader": "^9.5.0",
|
||||
"ts-protoc-gen": "^0.15.0",
|
||||
"typescript-json-serializer": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,162 @@ body {
|
|||
min-height: 100%;
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
body summary h2, body summary h3, body summary h4, body summary h5 {
|
||||
display: inline; }
|
||||
body converse-muc-sidebar {
|
||||
display: none !important; }
|
||||
body div.converse-container {
|
||||
body span.conquer-team-circle {
|
||||
display: inline-block;
|
||||
aspect-ratio: 1 / 1;
|
||||
height: 1rem;
|
||||
border-radius: 50%; }
|
||||
body div.conquer-team-to-select {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: beige;
|
||||
border: solid black; }
|
||||
body div.conquer-team-to-select button {
|
||||
height: 60px; }
|
||||
body p.conquer-register-error, body p.conquer-login-error, body p.conquer-login-success, body p.conquer-error {
|
||||
color: red;
|
||||
margin: 3px;
|
||||
font-size: 1.3rem;
|
||||
background: blanchedalmond;
|
||||
padding: 3px;
|
||||
border-radius: 10px;
|
||||
border: solid 1px black;
|
||||
overflow-y: scroll; }
|
||||
body form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%; }
|
||||
body form label {
|
||||
width: 100%; }
|
||||
body form label input, body form label textarea, body form label select {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background-image: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
background: white;
|
||||
box-shadow: none;
|
||||
min-height: 2rem;
|
||||
border-radius: 0.5rem; }
|
||||
body form label textarea {
|
||||
height: 100px; }
|
||||
body div.conquer-interface-element-padded {
|
||||
width: calc(100% - 60px);
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
display: flex;
|
||||
justify-content: center; }
|
||||
body div.conquer-interface-element-padded.conquer-display-block {
|
||||
display: block; }
|
||||
body div.conquer-interface-element-padded.conquer-display-none {
|
||||
display: none; }
|
||||
body div.fight-battle-selector-slide {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
height: 100px; }
|
||||
body div.fight-battle-selector-slide.conquer-display-none {
|
||||
display: none; }
|
||||
body div.fight-battle-selector-slide img {
|
||||
height: 50px;
|
||||
aspect-ratio: 1 / 1; }
|
||||
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%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
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;
|
||||
overflow-y: scroll; }
|
||||
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;
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
border-radius: 30px;
|
||||
background: darkseagreen;
|
||||
top: calc( 50% - 200px - 10px);
|
||||
left: calc( 50% - 150px - 10px);
|
||||
padding: 10px;
|
||||
height: 400px;
|
||||
margin-left: 0px; }
|
||||
body div.page-contents div.footer p.attribution {
|
||||
font-size: 0.8em; }
|
||||
body div.page-contents div.footer p.attribution a {
|
||||
font-size: 0.8em; }
|
||||
width: 300px;
|
||||
z-index: 1; }
|
||||
body div.conquer-login form, body div.conquer-register form {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center; }
|
||||
body .conquer-display-none {
|
||||
display: none; }
|
||||
body div.conquer-container {
|
||||
background: black;
|
||||
height: 100dvh;
|
||||
width: 100%; }
|
||||
body div.conquer-select-fight div.conquer-image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center; }
|
||||
body div.conquer-select-fight div.conquer-button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center; }
|
||||
body div.ol-control {
|
||||
display: none; }
|
||||
body span.round-center {
|
||||
background: blueviolet;
|
||||
color: #FEFEFA;
|
||||
|
@ -85,11 +229,9 @@ body {
|
|||
margin-right: 10px;
|
||||
display: flex; }
|
||||
body div.search a.search-icon {
|
||||
height: calc(100% - 40px);
|
||||
height: calc(100% - 28px);
|
||||
align-self: center;
|
||||
margin: 20px;
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
margin: 7px;
|
||||
display: flex;
|
||||
background: aliceblue;
|
||||
align-items: center;
|
||||
|
@ -133,11 +275,11 @@ body {
|
|||
height: 20%;
|
||||
width: 100%; }
|
||||
body div.carousel a {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
position: fixed;
|
||||
top: 80%;
|
||||
border: solid 3px black;
|
||||
width: calc(100% - 6px);
|
||||
height: calc(100% - 6px);
|
||||
height: calc(20% - 6px);
|
||||
left: 100%;
|
||||
transition: left 1s ease-in;
|
||||
font-size: 13px;
|
||||
|
@ -155,10 +297,9 @@ body {
|
|||
body div.carousel a:hover, body div.carousel a:focus {
|
||||
background: blueviolet;
|
||||
color: #f2eb8c; }
|
||||
body div.carousel a p {
|
||||
margin-bottom: 1px; }
|
||||
body div.carousel a h4 {
|
||||
margin: 0; }
|
||||
body div.carousel div.promoted-tag, body div.carousel h3 {
|
||||
margin: 0;
|
||||
margin-right: 5px; }
|
||||
body div.carousel img {
|
||||
margin: 10px;
|
||||
height: calc(100% - 20px);
|
||||
|
@ -218,7 +359,7 @@ body {
|
|||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 40%;
|
||||
top: 80px;
|
||||
top: 60px;
|
||||
height: calc(100% - 60px);
|
||||
z-index: 250;
|
||||
display: none; }
|
||||
|
@ -228,9 +369,9 @@ body {
|
|||
visibility: hidden;
|
||||
position: fixed;
|
||||
left: 100%;
|
||||
width: Min(70%, 600px);
|
||||
top: 80px;
|
||||
height: calc(100% - 80px);
|
||||
width: 70%;
|
||||
top: 60px;
|
||||
height: calc(100% - 60px);
|
||||
z-index: 500;
|
||||
transition: left 0.5s ease-in, visibility 0.5s ease-in;
|
||||
background: #FEFEFA; }
|
||||
|
@ -276,12 +417,12 @@ body {
|
|||
body a.menu-expand:hover .open-menu-icon, body a.menu-expand:focus .open-menu-icon, body a.menu-expand.active .open-menu-icon {
|
||||
display: none; }
|
||||
body nav > a.menu-expand > img {
|
||||
width: 40px;
|
||||
height: 40px; }
|
||||
width: 30px;
|
||||
height: 30px; }
|
||||
body nav > a > img.index-image-menu {
|
||||
vertical-align: middle;
|
||||
width: 60px;
|
||||
height: 60px; }
|
||||
width: 40px;
|
||||
height: 40px; }
|
||||
body nav > a > img.index-image-menu, body div.burguillos-logo-container > img {
|
||||
transition-property: transform;
|
||||
transition-duration: 2s;
|
||||
|
@ -292,7 +433,7 @@ body {
|
|||
body div.search-in-page {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
top: 60px;
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
@ -325,38 +466,24 @@ body {
|
|||
align-items: center;
|
||||
width: 100%;
|
||||
background: blueviolet;
|
||||
height: 80px;
|
||||
justify-content: start;
|
||||
flex-direction: row;
|
||||
height: 60px;
|
||||
top: 0%; }
|
||||
body nav.mobile-shortcuts a {
|
||||
height: 100%;
|
||||
width: 80px;
|
||||
width: 16.6666666667%;
|
||||
padding-left: 0;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0; }
|
||||
body nav.mobile-shortcuts a.go-to-index {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0; }
|
||||
body nav.mobile-shortcuts div.search {
|
||||
position: absolute;
|
||||
left: 80px;
|
||||
top: 10%;
|
||||
width: Min(calc(100% - 90px * 2), 500px);
|
||||
width: calc(100% * 4 / 6 - 20px);
|
||||
height: 80%;
|
||||
border-radius: 10px; }
|
||||
body nav.mobile-shortcuts a.menu-expand {
|
||||
position: absolute;
|
||||
left: Min(calc(100% - 80px), 600px);
|
||||
top: 0;
|
||||
align-self: end; }
|
||||
body div.page-contents {
|
||||
background: #FEFEFA;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
height: calc(80% - 80px);
|
||||
top: 60px;
|
||||
height: calc(80% - 60px);
|
||||
width: 100%;
|
||||
overflow-y: scroll; }
|
||||
body div.page-contents div.child-categories-mobile a {
|
||||
|
@ -516,14 +643,6 @@ body {
|
|||
body div.page-contents table th, body div.page-contents table td {
|
||||
font-size: 20px; } }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
body converse-muc-sidebar {
|
||||
display: flex !important; }
|
||||
body converse-muc-sidebar.hidden {
|
||||
display: none !important; }
|
||||
body div.converse-container {
|
||||
margin-left: 15px; } }
|
||||
|
||||
@media (min-width: 694px) {
|
||||
body div.carousel a {
|
||||
font-size: 20px; }
|
||||
|
@ -537,11 +656,37 @@ body {
|
|||
body div.page-contents img {
|
||||
max-width: 694px; } }
|
||||
|
||||
@media (min-width: 700px) {
|
||||
body nav.mobile-foldable {
|
||||
left: -100%; }
|
||||
body nav.mobile-foldable.show {
|
||||
left: 0; } }
|
||||
@media (min-width: 1100px) {
|
||||
body nav.mobile-foldable, body nav.mobile-foldable.show {
|
||||
display: none; }
|
||||
body nav.mobile-shortcuts {
|
||||
display: none; }
|
||||
body div.search-in-page.active {
|
||||
display: none; }
|
||||
body div.page-contents {
|
||||
top: 0%;
|
||||
left: 5%;
|
||||
height: 80%;
|
||||
width: 90%;
|
||||
border: solid 1px black; }
|
||||
body div.page-contents div.description.open-browser-container {
|
||||
margin-left: 0;
|
||||
margin-right: 0; }
|
||||
body div.page-contents div.description {
|
||||
margin-left: 10%;
|
||||
margin-right: 10%; }
|
||||
body div.page-contents nav.desktop {
|
||||
display: block;
|
||||
height: auto;
|
||||
height: 60px; }
|
||||
body div.page-contents nav.desktop a {
|
||||
display: table-cell;
|
||||
height: 60px; }
|
||||
body div.page-contents nav.desktop a img.index-image-menu {
|
||||
height: 40px;
|
||||
width: 40px; }
|
||||
body div.page-contents.no-carousel {
|
||||
height: 100%; } }
|
||||
|
||||
@media (min-width: 1333px) {
|
||||
body div.page-contents div.description div.articles a {
|
||||
|
@ -552,24 +697,6 @@ body {
|
|||
body div.page-contents div.description div.articles a:nth-child(3n+1) {
|
||||
margin-left: 0%; } }
|
||||
|
||||
@media (min-width: 848px) {
|
||||
body div.page-contents div.description div.articles a {
|
||||
width: 30%;
|
||||
margin-left: 3%; }
|
||||
body div.page-contents div.description div.articles a:nth-child(2n+1) {
|
||||
margin-left: 3%; }
|
||||
body div.page-contents div.description div.articles a:nth-child(3n+1) {
|
||||
margin-left: 0%; } }
|
||||
|
||||
@media (min-width: 1333px) {
|
||||
body div.page-contents div.description div.articles a {
|
||||
width: 22%;
|
||||
margin-left: 3%; }
|
||||
body div.page-contents div.description div.articles a:nth-child(3n+1) {
|
||||
margin-left: 3%; }
|
||||
body div.page-contents div.description div.articles a:nth-child(4n+1) {
|
||||
margin-left: 0%; } }
|
||||
|
||||
@media (max-width: 200px) {
|
||||
body {
|
||||
font-size: 20px; } }
|
||||
|
|
|
@ -10,7 +10,6 @@ $accent-secondary: #fde68f;
|
|||
$primary-secondary: #590e11;
|
||||
$background_sidebar: $background-page; //#F5F5DC;
|
||||
$color_sidebar: #dcdcf5;
|
||||
$attribution_font_size: 0.8em;
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
|
@ -18,25 +17,194 @@ html {
|
|||
}
|
||||
|
||||
body {
|
||||
summary {
|
||||
h2, h3, h4, h5 {
|
||||
display: inline;
|
||||
span.conquer-team-circle {
|
||||
display: inline-block;
|
||||
aspect-ratio: 1 / 1;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
div.conquer-team-to-select {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: beige;
|
||||
border: solid black;
|
||||
button {
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
converse-muc-sidebar {
|
||||
display: none !important;
|
||||
p.conquer-register-error, p.conquer-login-error, p.conquer-login-success,p.conquer-error {
|
||||
color: red;
|
||||
margin: 3px;
|
||||
font-size: 1.3rem;
|
||||
background: blanchedalmond;
|
||||
padding: 3px;
|
||||
border-radius: 10px;
|
||||
border: solid 1px black;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
div.converse-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
div.page-contents div.footer p.attribution {
|
||||
font-size: $attribution_font_size;
|
||||
a {
|
||||
font-size: $attribution_font_size;
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
label {
|
||||
width: 100%;
|
||||
input, textarea, select {
|
||||
width: 100%;
|
||||
border:none;
|
||||
background-image:none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
background: white;
|
||||
box-shadow: none;
|
||||
min-height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
textarea {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
div.conquer-interface-element-padded {
|
||||
width: calc(100% - 60px);
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&.conquer-display-block {
|
||||
display: block;
|
||||
}
|
||||
&.conquer-display-none {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
div.fight-battle-selector-slide {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
height: 100px;
|
||||
&.conquer-display-none {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
height: 50px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
}
|
||||
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%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
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;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
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%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
border: 1px solid black;
|
||||
position: fixed;
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
border-radius: 30px;
|
||||
background: darkseagreen;
|
||||
top: calc( 50% - 200px - 10px );
|
||||
left: calc( 50% - 150px - 10px );
|
||||
padding: 10px;
|
||||
height: 400px;
|
||||
width: 300px;
|
||||
z-index: 1;
|
||||
}
|
||||
.conquer-display-none {
|
||||
display: none;
|
||||
}
|
||||
div.conquer-container {
|
||||
background: black;
|
||||
height: 100dvh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.conquer-select-fight {
|
||||
div.conquer-image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
div.conquer-button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
div.ol-control {
|
||||
display: none;
|
||||
}
|
||||
span.round-center {
|
||||
background: $background_div;
|
||||
color: $background_sidebar;
|
||||
|
@ -128,11 +296,9 @@ body {
|
|||
display: flex;
|
||||
|
||||
a.search-icon {
|
||||
height: calc(100% - 40px);
|
||||
height: calc(100% - 28px);
|
||||
align-self: center;
|
||||
margin: 20px;
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
margin: 7px;
|
||||
display: flex;
|
||||
background: aliceblue;
|
||||
align-items: center;
|
||||
|
@ -200,11 +366,11 @@ body {
|
|||
width: 100%;
|
||||
|
||||
a {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
position: fixed;
|
||||
top: 80%;
|
||||
border: solid 3px black;
|
||||
width: calc(100% - 6px);
|
||||
height: calc(100% - 6px);
|
||||
height: calc(20% - 6px);
|
||||
left: 100%;
|
||||
transition: left 1s ease-in;
|
||||
|
||||
|
@ -227,12 +393,11 @@ body {
|
|||
background: $background_div;
|
||||
color: $color_div;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.promoted-tag, h3 {
|
||||
margin: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
img {
|
||||
|
@ -321,7 +486,7 @@ body {
|
|||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 40%;
|
||||
top: 80px;
|
||||
top: 60px;
|
||||
height: calc(100% - 60px);
|
||||
z-index: 250;
|
||||
display: none;
|
||||
|
@ -335,9 +500,9 @@ body {
|
|||
visibility: hidden;
|
||||
position: fixed;
|
||||
left: 100%;
|
||||
width: Min(70%, 600px);
|
||||
top: 80px;
|
||||
height: calc(100% - 80px);
|
||||
width: 70%;
|
||||
top: 60px;
|
||||
height: calc(100% - 60px);
|
||||
z-index: 500;
|
||||
transition: left 0.5s ease-in, visibility 0.5s ease-in;
|
||||
background: $background_sidebar;
|
||||
|
@ -411,14 +576,14 @@ body {
|
|||
}
|
||||
|
||||
nav > a.menu-expand > img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
nav > a > img.index-image-menu {
|
||||
vertical-align: middle;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
nav > a > img.index-image-menu, div.burguillos-logo-container > img {
|
||||
|
@ -435,7 +600,7 @@ body {
|
|||
div.search-in-page {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
top: 60px;
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
@ -478,48 +643,30 @@ body {
|
|||
align-items: center;
|
||||
width: 100%;
|
||||
background: $background_div;
|
||||
height: 80px;
|
||||
justify-content: start;
|
||||
flex-direction: row;
|
||||
height: 60px;
|
||||
top: 0%;
|
||||
|
||||
a {
|
||||
height: 100%;
|
||||
width: 80px;
|
||||
width: (100% / 6);
|
||||
padding-left: 0;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
a.go-to-index {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
div.search {
|
||||
position: absolute;
|
||||
left: 80px;
|
||||
top: 10%;
|
||||
width: Min(calc(100% - 90px * 2), 500px);
|
||||
width: calc(100% * 4 / 6 - 20px);
|
||||
height: 80%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
a.menu-expand {
|
||||
position: absolute;
|
||||
left: Min(calc(100% - 80px), 600px);
|
||||
top: 0;
|
||||
align-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
div.page-contents {
|
||||
background: $background-page;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
height: calc(80% - 80px);
|
||||
top: 60px;
|
||||
height: calc(80% - 60px);
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
|
@ -775,19 +922,6 @@ body {
|
|||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
body {
|
||||
converse-muc-sidebar {
|
||||
display: flex !important;
|
||||
&.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
div.converse-container {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 694px) {
|
||||
body {
|
||||
|
@ -822,14 +956,57 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
@media (min-width: 1100px) {
|
||||
body {
|
||||
nav.mobile-foldable {
|
||||
left: -100%;
|
||||
nav.mobile-foldable, nav.mobile-foldable.show {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav.mobile-shortcuts {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.search-in-page.active {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.page-contents {
|
||||
div.description.open-browser-container {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
nav.mobile-foldable.show {
|
||||
left: 0;
|
||||
|
||||
div.description {
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
}
|
||||
|
||||
nav.desktop {
|
||||
display: block;
|
||||
height: auto;
|
||||
height: 60px;
|
||||
|
||||
a {
|
||||
display: table-cell;
|
||||
height: 60px;
|
||||
|
||||
img.index-image-menu {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
top: 0%;
|
||||
left: 5%;
|
||||
height: 80%;
|
||||
width: 90%;
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
div.page-contents.no-carousel {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -856,51 +1033,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: 848px) {
|
||||
body {
|
||||
div.page-contents {
|
||||
div.description {
|
||||
div.articles {
|
||||
a {
|
||||
&:nth-child(2n+1) {
|
||||
margin-left: 3%;
|
||||
}
|
||||
|
||||
&:nth-child(3n+1) {
|
||||
margin-left: 0%;
|
||||
}
|
||||
|
||||
width: 30%;
|
||||
margin-left: 3%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1333px) {
|
||||
body {
|
||||
div.page-contents {
|
||||
div.description {
|
||||
div.articles {
|
||||
a {
|
||||
&:nth-child(3n+1) {
|
||||
margin-left: 3%;
|
||||
}
|
||||
|
||||
&:nth-child(4n+1) {
|
||||
margin-left: 0%;
|
||||
}
|
||||
|
||||
width: 22%;
|
||||
margin-left: 3%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 200px) {
|
||||
body {
|
||||
font-size: 20px;
|
||||
|
|
|
@ -1,371 +0,0 @@
|
|||
/*!
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Arturas Molcanovas <a.molcanovas@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
/*!
|
||||
localForage -- Offline Storage, Improved
|
||||
Version 1.10.0
|
||||
https://localforage.github.io/localForage
|
||||
(c) 2013-2017 Mozilla, Apache License 2.0
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Sizzle CSS Selector Engine v2.3.6
|
||||
* https://sizzlejs.com/
|
||||
*
|
||||
* Copyright JS Foundation and other contributors
|
||||
* Released under the MIT license
|
||||
* https://js.foundation/
|
||||
*
|
||||
* Date: 2021-02-16
|
||||
*/
|
||||
|
||||
/*!
|
||||
* URI.js - Mutating URLs
|
||||
*
|
||||
* Version: 1.19.11
|
||||
*
|
||||
* Author: Rodney Rehm
|
||||
* Web: http://medialize.github.io/URI.js/
|
||||
*
|
||||
* Licensed under
|
||||
* MIT License http://www.opensource.org/licenses/mit-license
|
||||
*
|
||||
*/
|
||||
|
||||
/*!
|
||||
* URI.js - Mutating URLs
|
||||
* IPv6 Support
|
||||
*
|
||||
* Version: 1.19.11
|
||||
*
|
||||
* Author: Rodney Rehm
|
||||
* Web: http://medialize.github.io/URI.js/
|
||||
*
|
||||
* Licensed under
|
||||
* MIT License http://www.opensource.org/licenses/mit-license
|
||||
*
|
||||
*/
|
||||
|
||||
/*!
|
||||
* URI.js - Mutating URLs
|
||||
* Second Level Domain (SLD) Support
|
||||
*
|
||||
* Version: 1.19.11
|
||||
*
|
||||
* Author: Rodney Rehm
|
||||
* Web: http://medialize.github.io/URI.js/
|
||||
*
|
||||
* Licensed under
|
||||
* MIT License http://www.opensource.org/licenses/mit-license
|
||||
*
|
||||
*/
|
||||
|
||||
/*!
|
||||
2020 Jason Mulligan <jason.mulligan@avoidwork.com>
|
||||
@version 7.0.0
|
||||
*/
|
||||
|
||||
/*!
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Arturas Molcanovas <a.molcanovas@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
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
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
/*! @license DOMPurify 2.3.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.6/LICENSE */
|
||||
|
||||
/*! https://mths.be/punycode v1.4.0 by @mathias */
|
||||
|
||||
/**
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description This is the form utilities module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright Alfredo Medrano Sánchez and the Converse.js contributors
|
||||
* @description
|
||||
* Component inspired by the one from fa-icons
|
||||
* https://github.com/obsidiansoft-io/fa-icons/blob/master/LICENSE
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright JC Brand
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description A plugin which restricts Converse to only one chat.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright Shachaf Ben-Kiki and the Converse.js contributors
|
||||
* @description
|
||||
* Started as a fork of Shachaf Ben-Kiki's jsgif library
|
||||
* https://github.com/shachaf/jsgif
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Converse.js plugin which add support for XEP-0206: XMPP Over BOSH
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Converse.js plugin which adds support for XEP-0198: Stream Management
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description This is the core utilities module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Converse.js plugin which shows a list of currently open
|
||||
* rooms in the "Rooms Panel" of the ControlBox.
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Converse.js plugin which add support for registering
|
||||
* an "App Server" as defined in XEP-0357
|
||||
* @copyright 2021, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Converse.js plugin which adds views for bookmarks specified in XEP-0048.
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description Converse.js (A browser based XMPP chat client)
|
||||
* @copyright 2021, The Converse developers
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description Converse.js plugin which adds views for XEP-0048 bookmarks
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description UI code XEP-0313 Message Archive Management
|
||||
* @copyright 2021, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description XEP-0313 Message Archive Management
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license MIT or GPL-2.0
|
||||
* @fileOverview Favico animations
|
||||
* @author Miroslav Magda, http://blog.ejci.net
|
||||
* @source: https://github.com/ejci/favico.js
|
||||
* @version 0.3.10
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-carbons
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Implements support for XEP-0280 Message Carbons
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-chatboxviews
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-dragresize
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-emoji
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-fullscreen
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-headlines-view
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-minimize
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-notification
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-pubsub
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-register
|
||||
* @description
|
||||
* This is a Converse.js plugin which add support for in-band registration
|
||||
* as specified in XEP-0077.
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module converse-rsm
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description XEP-0059 Result Set Management
|
||||
* Some code taken from the Strophe RSM plugin, licensed under the MIT License
|
||||
* Copyright 2006-2017 Strophe (https://github.com/strophe/strophejs)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module i18n
|
||||
* @copyright 2022, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description This is the internationalization module
|
||||
*/
|
||||
|
||||
/**
|
||||
* @preserve jed.js https://github.com/SlexAxton/Jed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clears the specified timeout and interval.
|
||||
* @method u#clearTimers
|
||||
* @param {number} timeout - Id if the timeout to clear.
|
||||
* @param {number} interval - Id of the interval to clear.
|
||||
* @private
|
||||
* @copyright Simen Bekkhus 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a {@link Promise} that resolves if the passed in function returns a truthy value.
|
||||
* Rejects if it throws or does not return truthy within the given max_wait.
|
||||
* @method u#waitUntil
|
||||
* @param {Function} func - The function called every check_delay,
|
||||
* and the result of which is the resolved value of the promise.
|
||||
* @param {number} [max_wait=300] - The time to wait before rejecting the promise.
|
||||
* @param {number} [check_delay=3] - The time to wait before each invocation of {func}.
|
||||
* @returns {Promise} A promise resolved with the value of func,
|
||||
* or rejected with the exception thrown by it or it times out.
|
||||
* @copyright Simen Bekkhus 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*@cc_on!@*/
|
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 7.1 KiB |
|
@ -1,90 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="192px" height="192px" viewBox="0 0 192 192" enable-background="new 0 0 192 192" xml:space="preserve"> <image id="image0" width="192" height="192" x="0" y="0"
|
||||
href="data:image/png;base64,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" />
|
||||
</svg>
|
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 37 KiB |
|
@ -1,310 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> <image id="image0" width="512" height="512" x="0" y="0"
|
||||
href="data:image/png;base64,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" />
|
||||
</svg>
|
Before Width: | Height: | Size: 24 KiB |
|
@ -1,19 +0,0 @@
|
|||
<svg class="converse-svg-logo"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 364 364">
|
||||
<title>Converse</title>
|
||||
<g class="cls-1" id="g904">
|
||||
<g data-name="Layer 2">
|
||||
<g data-name="Layer 7">
|
||||
<path
|
||||
class="cls-3"
|
||||
d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
|
||||
<path
|
||||
class="cls-4"
|
||||
d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.0 KiB |
|
@ -1,63 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 364 364"
|
||||
version="1.1">
|
||||
<title>Logo Converse</title>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="gradient"
|
||||
x1="92.14"
|
||||
y1="27.64"
|
||||
x2="267.65"
|
||||
y2="331.62"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#fff1d1"/>
|
||||
<stop
|
||||
offset="0.05"
|
||||
stop-color="#fae8c1"/>
|
||||
<stop
|
||||
offset="0.15"
|
||||
stop-color="#f0d5a1"/>
|
||||
<stop
|
||||
offset="0.27"
|
||||
stop-color="#e7c687"/>
|
||||
<stop
|
||||
offset="0.4"
|
||||
stop-color="#e1bb72"/>
|
||||
<stop
|
||||
offset="0.54"
|
||||
stop-color="#dcb264"/>
|
||||
<stop
|
||||
offset="0.71"
|
||||
stop-color="#daad5c"/>
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#d9ac59"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="2.3" result="blur1"/>
|
||||
<feOffset in="blur1" dx="3" dy="3" result="blur2"/>
|
||||
<feColorMatrix in="blur2" type="matrix" result="blur3"
|
||||
values="1 0 0 0 0.6
|
||||
0 1 0 0 0.6
|
||||
0 0 1 0 0.6
|
||||
0 0 0 1 0"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="blur3"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#shadow)">
|
||||
<path
|
||||
d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z"
|
||||
fill="#d9ac59"/>
|
||||
<path
|
||||
d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z"
|
||||
fill="url(#gradient)"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,11 +0,0 @@
|
|||
(self["webpackChunkconverse_js"] = self["webpackChunkconverse_js"] || []).push([[9210],{
|
||||
|
||||
/***/ 5903:
|
||||
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
|
||||
|
||||
!function(e,a){ true?module.exports=a(__webpack_require__(7484)):0}(this,(function(e){"use strict";function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=a(e),t={name:"af",weekdays:"Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag".split("_"),months:"Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember".split("_"),weekStart:1,weekdaysShort:"Son_Maa_Din_Woe_Don_Vry_Sat".split("_"),monthsShort:"Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des".split("_"),weekdaysMin:"So_Ma_Di_Wo_Do_Vr_Sa".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},relativeTime:{future:"oor %s",past:"%s gelede",s:"'n paar sekondes",m:"'n minuut",mm:"%d minute",h:"'n uur",hh:"%d ure",d:"'n dag",dd:"%d dae",M:"'n maand",MM:"%d maande",y:"'n jaar",yy:"%d jaar"}};return n.default.locale(t,null,!0),t}));
|
||||
|
||||
/***/ })
|
||||
|
||||
}]);
|
||||
//# sourceMappingURL=af-js.js.map
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"locales/dayjs/af-js.js","mappings":";;;;;AAAA,eAAe,KAAoD,kBAAkB,mBAAO,CAAC,IAAO,GAAG,CAA0I,CAAC,mBAAmB,aAAa,cAAc,+CAA+C,WAAW,cAAc,wZAAwZ,SAAS,UAAU,iHAAiH,eAAe,wLAAwL,qCAAqC","sources":["webpack://converse.js/./node_modules/dayjs/locale/af.js"],"sourcesContent":["!function(e,a){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=a(require(\"dayjs\")):\"function\"==typeof define&&define.amd?define([\"dayjs\"],a):(e=\"undefined\"!=typeof globalThis?globalThis:e||self).dayjs_locale_af=a(e.dayjs)}(this,(function(e){\"use strict\";function a(e){return e&&\"object\"==typeof e&&\"default\"in e?e:{default:e}}var n=a(e),t={name:\"af\",weekdays:\"Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag\".split(\"_\"),months:\"Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember\".split(\"_\"),weekStart:1,weekdaysShort:\"Son_Maa_Din_Woe_Don_Vry_Sat\".split(\"_\"),monthsShort:\"Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des\".split(\"_\"),weekdaysMin:\"So_Ma_Di_Wo_Do_Vr_Sa\".split(\"_\"),ordinal:function(e){return e},formats:{LT:\"HH:mm\",LTS:\"HH:mm:ss\",L:\"DD/MM/YYYY\",LL:\"D MMMM YYYY\",LLL:\"D MMMM YYYY HH:mm\",LLLL:\"dddd, D MMMM YYYY HH:mm\"},relativeTime:{future:\"oor %s\",past:\"%s gelede\",s:\"'n paar sekondes\",m:\"'n minuut\",mm:\"%d minute\",h:\"'n uur\",hh:\"%d ure\",d:\"'n dag\",dd:\"%d dae\",M:\"'n maand\",MM:\"%d maande\",y:\"'n jaar\",yy:\"%d jaar\"}};return n.default.locale(t,null,!0),t}));"],"names":[],"sourceRoot":""}
|
|
@ -1,11 +0,0 @@
|
|||
(self["webpackChunkconverse_js"] = self["webpackChunkconverse_js"] || []).push([[5073],{
|
||||
|
||||
/***/ 9911:
|
||||
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
|
||||
|
||||
!function(e,_){ true?module.exports=_(__webpack_require__(7484)):0}(this,(function(e){"use strict";function _(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=_(e),d={name:"am",weekdays:"እሑድ_ሰኞ_ማክሰኞ_ረቡዕ_ሐሙስ_አርብ_ቅዳሜ".split("_"),weekdaysShort:"እሑድ_ሰኞ_ማክሰ_ረቡዕ_ሐሙስ_አርብ_ቅዳሜ".split("_"),weekdaysMin:"እሑ_ሰኞ_ማክ_ረቡ_ሐሙ_አር_ቅዳ".split("_"),months:"ጃንዋሪ_ፌብሯሪ_ማርች_ኤፕሪል_ሜይ_ጁን_ጁላይ_ኦገስት_ሴፕቴምበር_ኦክቶበር_ኖቬምበር_ዲሴምበር".split("_"),monthsShort:"ጃንዋ_ፌብሯ_ማርች_ኤፕሪ_ሜይ_ጁን_ጁላይ_ኦገስ_ሴፕቴ_ኦክቶ_ኖቬም_ዲሴም".split("_"),weekStart:1,yearStart:4,relativeTime:{future:"በ%s",past:"%s በፊት",s:"ጥቂት ሰከንዶች",m:"አንድ ደቂቃ",mm:"%d ደቂቃዎች",h:"አንድ ሰዓት",hh:"%d ሰዓታት",d:"አንድ ቀን",dd:"%d ቀናት",M:"አንድ ወር",MM:"%d ወራት",y:"አንድ ዓመት",yy:"%d ዓመታት"},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM D ፣ YYYY",LLL:"MMMM D ፣ YYYY HH:mm",LLLL:"dddd ፣ MMMM D ፣ YYYY HH:mm"},ordinal:function(e){return e+"ኛ"}};return t.default.locale(d,null,!0),d}));
|
||||
|
||||
/***/ })
|
||||
|
||||
}]);
|
||||
//# sourceMappingURL=am-js.js.map
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"locales/dayjs/am-js.js","mappings":";;;;;AAAA,eAAe,KAAoD,kBAAkB,mBAAO,CAAC,IAAO,GAAG,CAA0I,CAAC,mBAAmB,aAAa,cAAc,+CAA+C,WAAW,cAAc,4VAA4V,mKAAmK,UAAU,wHAAwH,qBAAqB,eAAe,qCAAqC","sources":["webpack://converse.js/./node_modules/dayjs/locale/am.js"],"sourcesContent":["!function(e,_){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=_(require(\"dayjs\")):\"function\"==typeof define&&define.amd?define([\"dayjs\"],_):(e=\"undefined\"!=typeof globalThis?globalThis:e||self).dayjs_locale_am=_(e.dayjs)}(this,(function(e){\"use strict\";function _(e){return e&&\"object\"==typeof e&&\"default\"in e?e:{default:e}}var t=_(e),d={name:\"am\",weekdays:\"እሑድ_ሰኞ_ማክሰኞ_ረቡዕ_ሐሙስ_አርብ_ቅዳሜ\".split(\"_\"),weekdaysShort:\"እሑድ_ሰኞ_ማክሰ_ረቡዕ_ሐሙስ_አርብ_ቅዳሜ\".split(\"_\"),weekdaysMin:\"እሑ_ሰኞ_ማክ_ረቡ_ሐሙ_አር_ቅዳ\".split(\"_\"),months:\"ጃንዋሪ_ፌብሯሪ_ማርች_ኤፕሪል_ሜይ_ጁን_ጁላይ_ኦገስት_ሴፕቴምበር_ኦክቶበር_ኖቬምበር_ዲሴምበር\".split(\"_\"),monthsShort:\"ጃንዋ_ፌብሯ_ማርች_ኤፕሪ_ሜይ_ጁን_ጁላይ_ኦገስ_ሴፕቴ_ኦክቶ_ኖቬም_ዲሴም\".split(\"_\"),weekStart:1,yearStart:4,relativeTime:{future:\"በ%s\",past:\"%s በፊት\",s:\"ጥቂት ሰከንዶች\",m:\"አንድ ደቂቃ\",mm:\"%d ደቂቃዎች\",h:\"አንድ ሰዓት\",hh:\"%d ሰዓታት\",d:\"አንድ ቀን\",dd:\"%d ቀናት\",M:\"አንድ ወር\",MM:\"%d ወራት\",y:\"አንድ ዓመት\",yy:\"%d ዓመታት\"},formats:{LT:\"HH:mm\",LTS:\"HH:mm:ss\",L:\"DD/MM/YYYY\",LL:\"MMMM D ፣ YYYY\",LLL:\"MMMM D ፣ YYYY HH:mm\",LLLL:\"dddd ፣ MMMM D ፣ YYYY HH:mm\"},ordinal:function(e){return e+\"ኛ\"}};return t.default.locale(d,null,!0),d}));"],"names":[],"sourceRoot":""}
|
|
@ -1,11 +0,0 @@
|
|||
(self["webpackChunkconverse_js"] = self["webpackChunkconverse_js"] || []).push([[9406],{
|
||||
|
||||
/***/ 7200:
|
||||
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
|
||||
|
||||
!function(_,e){ true?module.exports=e(__webpack_require__(7484)):0}(this,(function(_){"use strict";function e(_){return _&&"object"==typeof _&&"default"in _?_:{default:_}}var t=e(_),d={name:"ar-dz",weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),months:"جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),weekdaysShort:"احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),monthsShort:"جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),weekdaysMin:"أح_إث_ثلا_أر_خم_جم_سب".split("_"),ordinal:function(_){return _},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiem:function(_){return _>12?"ص":"م"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"}};return t.default.locale(d,null,!0),d}));
|
||||
|
||||
/***/ })
|
||||
|
||||
}]);
|
||||
//# sourceMappingURL=ar-dz-js.js.map
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"locales/dayjs/ar-dz-js.js","mappings":";;;;;AAAA,eAAe,KAAoD,kBAAkB,mBAAO,CAAC,IAAO,GAAG,CAA6I,CAAC,mBAAmB,aAAa,cAAc,+CAA+C,WAAW,cAAc,sZAAsZ,SAAS,UAAU,gHAAgH,sBAAsB,oBAAoB,eAAe,uJAAuJ,qCAAqC","sources":["webpack://converse.js/./node_modules/dayjs/locale/ar-dz.js"],"sourcesContent":["!function(_,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=e(require(\"dayjs\")):\"function\"==typeof define&&define.amd?define([\"dayjs\"],e):(_=\"undefined\"!=typeof globalThis?globalThis:_||self).dayjs_locale_ar_dz=e(_.dayjs)}(this,(function(_){\"use strict\";function e(_){return _&&\"object\"==typeof _&&\"default\"in _?_:{default:_}}var t=e(_),d={name:\"ar-dz\",weekdays:\"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت\".split(\"_\"),months:\"جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر\".split(\"_\"),weekdaysShort:\"احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت\".split(\"_\"),monthsShort:\"جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر\".split(\"_\"),weekdaysMin:\"أح_إث_ثلا_أر_خم_جم_سب\".split(\"_\"),ordinal:function(_){return _},formats:{LT:\"HH:mm\",LTS:\"HH:mm:ss\",L:\"DD/MM/YYYY\",LL:\"D MMMM YYYY\",LLL:\"D MMMM YYYY HH:mm\",LLLL:\"dddd D MMMM YYYY HH:mm\"},meridiem:function(_){return _>12?\"ص\":\"م\"},relativeTime:{future:\"في %s\",past:\"منذ %s\",s:\"ثوان\",m:\"دقيقة\",mm:\"%d دقائق\",h:\"ساعة\",hh:\"%d ساعات\",d:\"يوم\",dd:\"%d أيام\",M:\"شهر\",MM:\"%d أشهر\",y:\"سنة\",yy:\"%d سنوات\"}};return t.default.locale(d,null,!0),d}));"],"names":[],"sourceRoot":""}
|