Compare commits
7 Commits
c313194e15
...
search
| Author | SHA1 | Date | |
|---|---|---|---|
| cb39695e6c | |||
| 487995366e | |||
| f9daaefbdd | |||
| 743bbc2f3b | |||
| 0de6158652 | |||
| 692ef13574 | |||
| f83f95a51a |
@@ -59,4 +59,4 @@ Photo Sphere Viewer, Three.js (da cui dipende Photo Sphere Viewer) e Stimulus.
|
||||
## TODO
|
||||
|
||||
- [ ] Auto-discovery per Stimulus?
|
||||
- [ ] Refactor con app state per evitare oggetti globali
|
||||
- [x] Refactor con app state per evitare oggetti globali
|
||||
@@ -187,3 +187,8 @@ a:visited {
|
||||
.marker-cluster-small span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Apply Bulma's is-danger color based on state */
|
||||
input:invalid {
|
||||
border-color: #ff6685;
|
||||
}
|
||||
BIN
img/pub/AC_36-1.jpg
Executable file
BIN
img/pub/AC_36-1.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
16
index.html
16
index.html
@@ -498,6 +498,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="media mt-4 pb-6">
|
||||
<figure class="media-left">
|
||||
<p class="image is-128x128">
|
||||
<img src="img/pub/AC_36-1.jpg" />
|
||||
</p>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<p>
|
||||
Caratelli G., Giorgi C., Paraciani N. 2025,
|
||||
<em>From archaeological survey to data accessibility: a WebGIS for the island of Capri</em>,
|
||||
«Archeologia e Calcolatori», 36.1, 65-86 (<a href="https://doi.org/10.19282/ac.36.1.2025.04">https://doi.org/10.19282/ac.36.1.2025.04</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
</footer>
|
||||
|
||||
@@ -65,9 +65,9 @@
|
||||
</button>
|
||||
<button class="button is-white mr-2 mt-1" title="Cerca"
|
||||
data-id="search"
|
||||
data-action="modal#open">
|
||||
<span class="icon">
|
||||
<i class="fa fa-search"></i>
|
||||
data-action="menu#toggleMenu">
|
||||
<span class="icon" data-id="search" data-action="menu#toggleMenu">
|
||||
<i data-id="search" class="fa fa-search"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -79,7 +79,74 @@
|
||||
<div class="main columns">
|
||||
<div class="column mb-0 pb-0 is-full is-relative">
|
||||
<div class="pb-0 is-relative" id="map" aria-describedby="map-progress" aria-busy="true">
|
||||
<progress id="map-progress" class="p-2 progress is-medium is-link" aria-label="Map loading..." />
|
||||
<progress id="map-progress" class="p-2 progress is-medium is-link" aria-label="Caricamento mappa..." />
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-overlay column is-hidden is-4 is-4-desktop is-5-mobile is-pulled-right is-overlay has-background-white-ter"
|
||||
data-menu-target="search" data-controller="layer search">
|
||||
<button title="Chiudi ricerca" class="delete is-pulled-right" data-action="menu#closeSearch"></button>
|
||||
<h1 class="is-size-5">Ricerca</h1>
|
||||
<form id="search-form" method="POST" data-search-target="search" data-action="submit->search#submitSearch">
|
||||
<div class="field">
|
||||
<label class="label">Testo libero</label>
|
||||
<div class="control is-full-width">
|
||||
<input class="input" type="text" minlength="3" name="text" placeholder="Inserire parole chiave" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Categoria sito</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="category">
|
||||
<option default value="">-- Scegliere la categoria del sito --</option>
|
||||
<option value="site">Sito conservato</option>
|
||||
<!--<option value="not_conserved">Sito non conservato</option>-->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Tecnica muraria</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="technique">
|
||||
<option default value="">-- Scegliere tecnica --</option>
|
||||
<option>Opera poligonale</option>
|
||||
<option>Opera incerta</option>
|
||||
<option>Opera reticolata</option>
|
||||
<option>Opera laterizia</option>
|
||||
<option>Opera mista</option>
|
||||
<option>Opera cementizia</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped mt-5 mb-0 pb-0">
|
||||
<div class="control">
|
||||
<button class="button is-link" type="submit">
|
||||
<span>Cerca</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-search"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-link is-light" type="reset" data-action="search#clearSearch">
|
||||
<span>Cancella filtri</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-times"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="content pt-2 mt-3 is-hidden" data-search-target="container">
|
||||
<table class="table is-fullwidth is-striped">
|
||||
<thead>
|
||||
<tr><td class="has-text-centered has-text-weight-bold is-size-5" colspan="2">Risultati</td></tr>
|
||||
</thead>
|
||||
<tbody data-search-target="results"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-overlay column is-hidden is-4 is-4-desktop is-5-mobile is-pulled-right is-overlay has-background-white-ter"
|
||||
@@ -312,85 +379,6 @@
|
||||
<button title="Chiudi menu" class="delete is-pulled-right" data-action="menu#closeCartography"></button>
|
||||
</aside>
|
||||
</div>
|
||||
<!-- Search modal -->
|
||||
<div class="modal" id="search" data-modal-target="modal">
|
||||
<div class="modal-background" data-action="click->modal#close click->tabs#reset"></div>
|
||||
<div class="modal-content box has-background-white pt-4 mr-4 ml-4 pl-4 pr-4" style="min-height: 400px;">
|
||||
<h1 class="is-size-4 has-text-centered">Ricerca</h1>
|
||||
<div class="field">
|
||||
<label class="label">Testo libero</label>
|
||||
<div class="control is-full-width">
|
||||
<input class="input" type="text" placeholder="Inserire parole chiave">
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns mt-5 pt-3">
|
||||
<div class="field column">
|
||||
<label class="label">Categoria sito</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select>
|
||||
<option default>-- Scegliere la categoria del sito --</option>
|
||||
<option>Sito conservato</option>
|
||||
<option>Sito non conservato</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="field column">
|
||||
<label class="label">Categoria reperto</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select>
|
||||
<option default>-- Scegliere la categoria del reperto --</option>
|
||||
<option>Scultura</option>
|
||||
<option>Epigrafe</option>
|
||||
<option>Elemento architettonico</option>
|
||||
<option>Decorazione parietale</option>
|
||||
<option>Pavimentazione</option>
|
||||
<option>Arredo</option>
|
||||
<option>Abbigliamento e ornamenti personali</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div class="field column">
|
||||
<label class="label">Tecnica muraria</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select>
|
||||
<option default>-- Scegliere tecnica --</option>
|
||||
<option>Opera poligonale</option>
|
||||
<option>Opera incerta</option>
|
||||
<option>Opera reticolata</option>
|
||||
<option>Opera laterizia</option>
|
||||
<option>Opera mista</option>
|
||||
<option>Opera cementizia</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped mt-5 mb-0 pb-0 has-text-right">
|
||||
<div class="control">
|
||||
<button class="button is-link">
|
||||
<span>Cerca</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-search"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-link is-light">
|
||||
<span>Cancella filtri</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-times"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bibliography citations template -->
|
||||
|
||||
@@ -6,6 +6,7 @@ export default class extends Controller {
|
||||
'list',
|
||||
'menu',
|
||||
'cartography',
|
||||
'search',
|
||||
'icon'
|
||||
];
|
||||
|
||||
@@ -86,12 +87,17 @@ export default class extends Controller {
|
||||
toggleMenu(event) {
|
||||
const menuId = event.target.dataset.id;
|
||||
|
||||
console.debug(menuId, event.target);
|
||||
|
||||
// Stupid...
|
||||
if (menuId === 'main') {
|
||||
this.menuTarget.classList.toggle('is-hidden');
|
||||
if (!this.cartographyTarget.classList.contains('is-hidden')) {
|
||||
this.cartographyTarget.classList.add('is-hidden');
|
||||
}
|
||||
if (!this.searchTarget.classList.contains('is-hidden')) {
|
||||
this.searchTarget.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (menuId === 'cartography') {
|
||||
@@ -99,6 +105,18 @@ export default class extends Controller {
|
||||
if (!this.menuTarget.classList.contains('is-hidden')) {
|
||||
this.menuTarget.classList.add('is-hidden');
|
||||
}
|
||||
if (!this.searchTarget.classList.contains('is-hidden')) {
|
||||
this.searchTarget.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
if (menuId === 'search') {
|
||||
this.searchTarget.classList.toggle('is-hidden');
|
||||
if (!this.menuTarget.classList.contains('is-hidden')) {
|
||||
this.menuTarget.classList.add('is-hidden');
|
||||
}
|
||||
if (!this.cartographyTarget.classList.contains('is-hidden')) {
|
||||
this.cartographyTarget.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +128,10 @@ export default class extends Controller {
|
||||
this.cartographyTarget.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
closeSearch() {
|
||||
this.searchTarget.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
toggleList(id) {
|
||||
document.querySelector(`#${id}`).classList.toggle('is-hidden');
|
||||
}
|
||||
|
||||
139
webgis/js/controllers/search_controller.js
Normal file
139
webgis/js/controllers/search_controller.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
import { GisState } from "../state.js";
|
||||
import UI from "../ui.js";
|
||||
|
||||
const html = String.raw;
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [
|
||||
'search',
|
||||
'results',
|
||||
'clear',
|
||||
'container',
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
async submitSearch(event) {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.target);
|
||||
const body = {};
|
||||
const map = GisState.map;
|
||||
const techs = GisState.layers.buildingTechs;
|
||||
const techsMarkers = GisState.markers.buildingTechs;
|
||||
|
||||
// Reset search for building techs...
|
||||
for (const key of Object.keys(techsMarkers)) {
|
||||
map.removeLayer(techsMarkers[key]);
|
||||
}
|
||||
|
||||
for (const entry of data.entries()) {
|
||||
body[entry[0]] = entry[1];
|
||||
}
|
||||
|
||||
const response = await fetch(`${GisState.apiUrl}/search?` + new URLSearchParams(body));
|
||||
const results = await response.json();
|
||||
|
||||
console.warn(body);
|
||||
|
||||
this.containerTarget.classList.remove('is-hidden');
|
||||
this.#injectResults(results);
|
||||
if (results.length) {
|
||||
this.#filterMap(results);
|
||||
// Should technique always be shown after a search?
|
||||
for (const key of Object.keys(techsMarkers)) {
|
||||
if (techsMarkers[key].options.label === body.technique)
|
||||
map.addLayer(techsMarkers[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
const map = GisState.map;
|
||||
|
||||
// Restore layer groups in map
|
||||
for (const key of Object.keys(GisState.layers)) {
|
||||
map.addLayer(GisState.layers[key]);
|
||||
}
|
||||
|
||||
// Empty result set
|
||||
this.resultsTarget.innerHTML = '';
|
||||
this.containerTarget.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
#injectResults(results) {
|
||||
/**
|
||||
* @type {HTMLOutputElement} output
|
||||
*/
|
||||
const output = this.resultsTarget;
|
||||
output.innerHTML = '';
|
||||
|
||||
if (results.length === 0) {
|
||||
output.innerHTML = html`
|
||||
<p class="has-background-white-bis p-4 mt-0 has-text-centered">
|
||||
Nessun risultato trovato per i parametri di ricerca
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
const sites = GisState.markers.sites;
|
||||
|
||||
for (const result of results) {
|
||||
let coordinates = ''
|
||||
for (let key of Object.keys(sites)) {
|
||||
if (sites[key].options.data.label === result.label) {
|
||||
coordinates = key;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO The group value should be dynamic!!
|
||||
const item = html`
|
||||
<tr>
|
||||
<td class="pt-4">${result.label}</td>
|
||||
<td>
|
||||
<button class="button is-link"
|
||||
data-controller="marker"
|
||||
data-action="marker#go"
|
||||
data-marker-coords-value="${coordinates}"
|
||||
data-marker-group-value="sites">
|
||||
Vai al sito
|
||||
<span class="ml-1 icon">
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
output.innerHTML += item;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Array<Object>} results
|
||||
*/
|
||||
#filterMap(results) {
|
||||
const map = GisState.map;
|
||||
const labels = [];
|
||||
results.forEach(r => labels.push(r.label));
|
||||
|
||||
// Remove all layer groups first
|
||||
for (const key of Object.keys(GisState.layers)) {
|
||||
map.removeLayer(GisState.layers[key]);
|
||||
}
|
||||
|
||||
const sites = GisState.markers.sites;
|
||||
|
||||
for (let key of Object.keys(sites)) {
|
||||
// If map has layers from previous search results...
|
||||
map.removeLayer(sites[key]);
|
||||
for (const label of labels) {
|
||||
if (sites[key].options.data.label === label) {
|
||||
map.addLayer(sites[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) {
|
||||
|
||||
await this.addLayerGroups(map);
|
||||
await this.fetchCartographyLayers();
|
||||
const buildingTechs = await this.buildingTechs();
|
||||
|
||||
const reprojectedWMSLayer = GIS.reprojectWMS();
|
||||
const wmsLayer = new reprojectedWMSLayer(
|
||||
@@ -94,6 +95,7 @@ GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) {
|
||||
'Fabbricati' : buildings,
|
||||
'Vincoli archeologici' : layerVincoli,
|
||||
'Vincoli archeologici indiretti' : layerPaesistici,
|
||||
'Tecniche murarie' : buildingTechs,
|
||||
};
|
||||
L.control.layers(baseMap, cartography).addTo(map);
|
||||
|
||||
@@ -114,6 +116,35 @@ GIS.fetchCartographyLayers = async function () {
|
||||
GisState.cartography.historic.push({id, label});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create building techs layer
|
||||
* @returns {L.Layer}
|
||||
*/
|
||||
GIS.buildingTechs = async function () {
|
||||
let techsData = await fetch(`${API_URL}/building_techs`)
|
||||
.then(data => data.json());
|
||||
|
||||
let techs = new L.LayerGroup();
|
||||
|
||||
for (let record of techsData) {
|
||||
const marker = L.marker(
|
||||
record.coordinates,
|
||||
{icon: Icons.techs, label: record.technique}
|
||||
)
|
||||
.bindTooltip(record.technique)
|
||||
.bindPopup(UI.createBuildingTechTable(record));
|
||||
|
||||
techs.addLayer(marker);
|
||||
const markerLabel = `${record.coordinates[0]} ${record.coordinates[1]}`;
|
||||
marker.options.data = record;
|
||||
marker.options.site = record.site.label;
|
||||
GisState.markers.buildingTechs[markerLabel] = marker;
|
||||
}
|
||||
|
||||
GisState.layers.buildingTechs = techs;
|
||||
|
||||
return techs;
|
||||
}
|
||||
/**
|
||||
* Load georeferenced image overlays layer group
|
||||
* @param {Number} imageId - The API id of the georeferenced image
|
||||
|
||||
@@ -59,4 +59,6 @@ Icons.reuse = L.icon(
|
||||
|
||||
Icons.camera = L.divIcon({className: 'fa fa-camera'});
|
||||
|
||||
Icons.techs = L.divIcon({className: 'fa fa-circle has-text-primary-25'});
|
||||
|
||||
export default Icons;
|
||||
@@ -1,5 +1,6 @@
|
||||
import GIS from './gis.js';
|
||||
import UI from './ui.js';
|
||||
import { GisState } from './state.js';
|
||||
import { Application } from '@hotwired/stimulus';
|
||||
import MenuController from './controllers/menu_controller.js';
|
||||
import ModalController from './controllers/modal_controller.js';
|
||||
@@ -7,6 +8,7 @@ import MarkerController from './controllers/marker_controller.js';
|
||||
import BiblioController from './controllers/biblio_controller.js';
|
||||
import TabsController from './controllers/tabs_controller.js';
|
||||
import LayerController from './controllers/layer_controller.js';
|
||||
import SearchController from './controllers/search_controller.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Register Stimulus controllers
|
||||
@@ -14,14 +16,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
let progress = document.querySelector('progress');
|
||||
const map = await GIS.initMap('map');
|
||||
progress.classList.add('is-hidden');
|
||||
|
||||
map._container.setAttribute('aria-busy', false);
|
||||
|
||||
// Trigger Stimulus buildMenu method...
|
||||
const menuEvent = new Event('menu-ready');
|
||||
document.dispatchEvent(menuEvent);
|
||||
|
||||
progress.classList.add('is-hidden');
|
||||
map._container.setAttribute('aria-busy', false);
|
||||
|
||||
GIS.toggleSpherical(map);
|
||||
|
||||
UI.addCenterMapControl(map, GIS.CENTER_COORDS, GIS.INIT_ZOOM);
|
||||
@@ -36,4 +38,5 @@ function initStimulus() {
|
||||
Stimulus.register("biblio", BiblioController);
|
||||
Stimulus.register("tabs", TabsController);
|
||||
Stimulus.register("layer", LayerController);
|
||||
Stimulus.register("search", SearchController);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export const GisState = {
|
||||
prehistoric: {},
|
||||
underwater: {},
|
||||
reuse: {},
|
||||
buildingTechs: {},
|
||||
},
|
||||
layers: {
|
||||
sites: {},
|
||||
@@ -36,6 +37,7 @@ export const GisState = {
|
||||
prehistoric: {},
|
||||
underwater: {},
|
||||
reuse: {},
|
||||
buildingTechs: {},
|
||||
},
|
||||
bibliography: null,
|
||||
apiUrl : null,
|
||||
|
||||
@@ -12,6 +12,8 @@ import { Underwater } from './components/Underwater.js';
|
||||
import { GisState } from "./state.js";
|
||||
import { Reuse } from './components/Reuse.js';
|
||||
|
||||
const html = String.raw;
|
||||
|
||||
/**
|
||||
* @namespace UI
|
||||
*/
|
||||
@@ -270,5 +272,23 @@ UI.imageGallery = function (galleryId, items, video = false) {
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Object} record
|
||||
* @returns {string}
|
||||
*/
|
||||
UI.createBuildingTechTable = function(record) {
|
||||
return html`
|
||||
<table class="table is-striped is-size-6 m-2">
|
||||
<tbody>
|
||||
<tr><th>Tecnica</th><td>${record.technique}</td></tr>
|
||||
<tr><th>Descrizione</th><td>${record.description}</td></tr>
|
||||
<tr><th>Funzione</th><td>${record.function}</td></tr>
|
||||
<tr><th>Materiale</th><td>${record.material}</td></tr>
|
||||
<tr><th>Sito</th><td>${record.site.label}</td></tr>
|
||||
<tr><th>Comune</th><td>${record.municipality}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
;`
|
||||
}
|
||||
|
||||
export default UI;
|
||||
|
||||
Reference in New Issue
Block a user