490 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			490 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| const MAPBOX_TOKEN = 'pk.eyJ1Ijoibmljb3BhIiwiYSI6ImNsZmNiZGN0ZTJzbGgzdG8xYnZxOXRvd28ifQ.nvK1VYF6lwPpA094cL83KQ';
 | |
| /**
 | |
| * @namespace DataSpace
 | |
| */
 | |
| const DataSpace = {};
 | |
| 
 | |
| DataSpace.BASE_URL = 'http://dataspace.ispc.cnr.it';
 | |
| DataSpace.RES_ENDPOINT = '/resources/';
 | |
| DataSpace.FILES_URI = `${DataSpace.BASE_URL}/files/uploadedfiles/`;
 | |
| DataSpace.OBJECT_ORDER = {
 | |
|     "Object Type" : null,
 | |
|     "Object ID" : null,
 | |
|     "Object Excavation code" : null,
 | |
|     "Object Chronology" : null,
 | |
|     "Object Era" : null,
 | |
|     "Object Geographical Context of Discovery" : null,
 | |
|     "Object Dimensions" : null,
 | |
|     "Object Material" : null,
 | |
|     "Object Description" : null,
 | |
|     "Object Conservation State" : null,
 | |
|     "Object Reused?" : null,
 | |
|     "Object Project" : null,
 | |
|     "Object Compiler" : null,
 | |
|     "Object Bibliography" : null,
 | |
| };
 | |
| DataSpace.CONTEXT_ORDER = {
 | |
|     "Context Name" : null,
 | |
|     "Context Typology" : null,
 | |
|     "Context Chronology" : null,
 | |
|     "Context Era" : null,
 | |
|     "Context Surface" : null,
 | |
|     "Context Square meters" : null,
 | |
|     "Context Quality of the marble used/extracted" : null,
 | |
|     "Context Description" : null,
 | |
|     "Context Traces of extraction tools" : null,
 | |
|     "Context Amount of debris (cm3)" : null,
 | |
|     "Context Presence of unfinished materials" : null,
 | |
| };
 | |
| const OBJECT_REPORT = new Map();
 | |
| OBJECT_REPORT.set(
 | |
|     'before-gallery',
 | |
|     {
 | |
|         "Object Type" : null,
 | |
|         "Object ID" : null,
 | |
|         "Object Excavation code" : null,
 | |
|         "Object Chronology" : null,
 | |
|         "Object Era" : null,
 | |
|         "Object Geographical Context of Discovery" : null,
 | |
|         "Object Dimensions" : null,
 | |
|         "Object Material" : null,
 | |
|     }
 | |
| );
 | |
| OBJECT_REPORT.set(
 | |
|     'after-gallery-1-col',
 | |
|     {
 | |
|         "Object Description" : null,
 | |
|         "Object Conservation State" : null,
 | |
|     }
 | |
| );
 | |
| OBJECT_REPORT.set(
 | |
|     'after-gallery-2-col',
 | |
|     {
 | |
|         "Object Reused?" : null,
 | |
|         "Object Project" : null,
 | |
|         "Object Compiler" : null,
 | |
|         "Object Bibliography" : null,
 | |
|     }
 | |
| );
 | |
| const CONTEXT_REPORT = new Map();
 | |
| CONTEXT_REPORT.set(
 | |
|     'before-gallery',
 | |
|     {
 | |
|         "Context Name" : null,
 | |
|         "Context Typology" : null,
 | |
|         "Context Chronology" : null,
 | |
|         "Context Era" : null,
 | |
|         "Context Surface" : null,
 | |
|         "Context Square meters" : null,
 | |
|         "Context Quality of the marble used/extracted" : null,
 | |
|         "Context Traces of extraction tools" : null,
 | |
|         "Context Amount of debris (cm3)" : null,
 | |
|         "Context Presence of unfinished materials" : null,
 | |
|     }
 | |
| );
 | |
| CONTEXT_REPORT.set(
 | |
|     'after-gallery-1-col',
 | |
|     {
 | |
|         "Context Description" : null,
 | |
|     }
 | |
| );
 | |
| CONTEXT_REPORT.set(
 | |
|     'after-gallery-2-col',
 | |
|     {
 | |
|     }
 | |
| );
 | |
| DataSpace.OBJECT_REPORT = OBJECT_REPORT;
 | |
| DataSpace.CONTEXT_REPORT = CONTEXT_REPORT;
 | |
| DataSpace.RESOURCE_REPORT = {
 | |
|     'Object' : DataSpace.OBJECT_REPORT,
 | |
|     'Context' : DataSpace.CONTEXT_REPORT,
 | |
| };
 | |
| /**
 | |
|  * Populate partial objects from
 | |
|  * resource object based on Map
 | |
|  * @todo
 | |
|  * @param {object} resource
 | |
|  * @param {string} resType
 | |
|  * 
 | |
|  * @return {Map<string, object>}
 | |
|  */
 | |
| DataSpace.createShape = function (resource, resType) {
 | |
|     const shape = this.RESOURCE_REPORT[resType];
 | |
| 
 | |
|     let beforeGallery = shape.get('before-gallery'),
 | |
|         afterGalleryCol1 = shape.get('after-gallery-1-col'),
 | |
|         afterGalleryCol2 = shape.get('after-gallery-2-col');
 | |
| 
 | |
|     // TODO export to private function
 | |
|     for (const key in shape.get('before-gallery')) {
 | |
|         if (resource[key]) {
 | |
|             beforeGallery[key] = resource[key];
 | |
|         } else {
 | |
|             delete beforeGallery[key];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for (const key in shape.get('after-gallery-1-col')) {
 | |
|         if (resource[key]) {
 | |
|             afterGalleryCol1[key] = resource[key];
 | |
|         } else {
 | |
|             delete afterGalleryCol1[key];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for (const key in shape.get('after-gallery-2-col')) {
 | |
|         if (resource[key]) {
 | |
|             afterGalleryCol2[key] = resource[key];
 | |
|         } else {
 | |
|             delete afterGalleryCol2[key];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     shape.set('before-gallery', beforeGallery);
 | |
|     shape.set('after-gallery-1-col', afterGalleryCol1);
 | |
|     shape.set('after-gallery-2-col', afterGalleryCol2);
 | |
| 
 | |
|     return shape;
 | |
| }
 | |
| /**
 | |
|  * @todo Refactor!! Make it general...
 | |
|  *
 | |
|  * @param {object} report The report's JSON object
 | |
|  * @param {string[]} images Filenames of images
 | |
|  *
 | |
|  * @return {void}
 | |
|  */
 | |
| DataSpace.renderReport = function (report, images)
 | |
| {
 | |
|     // TODO
 | |
|     let resource = report.resource; 
 | |
|     let resKeys = Object.keys(resource);
 | |
|     // TODO
 | |
|     if (!resKeys.length) {
 | |
|         location.href = '/404.html';
 | |
|         return;
 | |
|     }
 | |
|     
 | |
|     let resType = resKeys[0].split(' ')[0]; 
 | |
| 
 | |
|     const reportOrder = {
 | |
|         'Object' : this.OBJECT_ORDER,
 | |
|         'Context' : this.CONTEXT_ORDER,
 | |
|     };
 | |
| 
 | |
|     resource = Object.assign(reportOrder[resType], resource);
 | |
|     const geoJSON = JSON.parse(
 | |
|         resource[`${resType} Coordinates`]
 | |
|             .replaceAll('\'', '"')
 | |
|     );
 | |
| 
 | |
|     // TODO this is terrible...
 | |
|     const centerCoords = this.getCenterCoordinates(geoJSON);
 | |
|     this.createMap(geoJSON);
 | |
| 
 | |
|     // Write coordinates below map
 | |
|     document.querySelector('#coord').innerHTML = `
 | |
|         <span><strong>Latitude:</strong> ${centerCoords[0]}</span>
 | |
|         <span><strong>Longitude:</strong> ${centerCoords[1]}</span>
 | |
|     `;
 | |
| 
 | |
|     resKeys = resKeys.filter(e => !e.includes('Coordinates'));
 | |
| 
 | |
|     document.querySelector('#rep-tit')
 | |
|         .innerText = `${resType} ${report.displayname}`;
 | |
| 
 | |
|     const shape = this.createShape(resource, resType);
 | |
| 
 | |
|     _createReportTable(resType, shape, resource);
 | |
| 
 | |
|     if (images.length) {
 | |
|         _createImgGallery(images, 'gallery');
 | |
|     }
 | |
| 
 | |
|     // Create after-gallery...
 | |
|     _createReportTail(resType, shape, resource);
 | |
| }
 | |
| /**
 | |
|  * Fetch JSON report...
 | |
|  * @param {string} uuid The resource's UUID in Arches
 | |
|  * @param {string} format Either 'json' or 'arches-json'
 | |
|  * 
 | |
|  * @return {object}
 | |
|  */
 | |
| DataSpace.fetchReport = async function (uuid, format='json')
 | |
| {
 | |
|     return await fetch(
 | |
|             `${this.BASE_URL}${this.RES_ENDPOINT}${uuid}?format=${format}&indent=2`
 | |
|         )
 | |
|         .then(res => res.json())
 | |
|         .catch(excep => {
 | |
|             _fetchError(excep, 'error');
 | |
|             document.querySelector('.modal')
 | |
|                 .classList.remove('active');
 | |
|         });
 | |
| }
 | |
| /**
 | |
|  * Add window.print to link in navbar
 | |
|  * 
 | |
|  * @return {void}
 | |
|  */
 | |
| DataSpace.printReport = function () {
 | |
|     document.querySelector('#print')
 | |
|         .addEventListener('click', () => {
 | |
|             window.print();
 | |
|     });
 | |
| }
 | |
| /**
 | |
|  * Calculate center coordinates
 | |
|  * based on feature geometry
 | |
|  * 
 | |
|  * @param {object} geoJSON  The geoJSON feature
 | |
|  * @returns {string[]}
 | |
|  */
 | |
| DataSpace.getCenterCoordinates = function (geoJSON)
 | |
| {
 | |
|     const geometry = geoJSON.features[0].geometry.type;
 | |
| 
 | |
|     let coordinates = geometry === 'Point' ?
 | |
|         geoJSON.features[0].geometry.coordinates : 
 | |
|        geoJSON.features[0].geometry.coordinates[0];
 | |
|     
 | |
|     let centerCoords = [coordinates[1], coordinates[0]];
 | |
|     
 | |
|     if (geometry !== 'Point') {
 | |
|         let avX = coordinates[0]
 | |
|             .map(el => el[0])
 | |
|             .reduce((p, c) => p + c) / coordinates[0].length;
 | |
| 
 | |
|         let avY = coordinates[0]
 | |
|             .map(el => el[1])
 | |
|             .reduce((p, c) => p + c) / coordinates[0].length;
 | |
|         
 | |
|         centerCoords = [avY, avX];
 | |
|     }
 | |
|     
 | |
|     return centerCoords;
 | |
| }
 | |
| /**
 | |
|  * @todo Use OpenLayers?
 | |
|  * Attach Leaflet.js map to HTML element
 | |
|  * and return center coordinates (NOOOO)
 | |
|  * 
 | |
|  * @param {string} geoJSON 
 | |
|  * @param {string} htmlId 
 | |
|  * 
 | |
|  * @return {string[]}
 | |
|  */
 | |
| DataSpace.createMap = function (geoJSON, htmlId = 'map') {
 | |
|     const centerCoords = this.getCenterCoordinates(geoJSON);
 | |
|     const mapboxAttribution = `© <a href="https://www.mapbox.com/about/maps/">Mapbox</a>`;
 | |
|     const mapboxSat = `https://api.mapbox.com/v4/{id}/{z}/{x}/{y}@2x.jpg90?access_token=${MAPBOX_TOKEN}`;
 | |
|     const streets = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
 | |
|         maxZoom: 18,
 | |
|         attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
 | |
|     });
 | |
|     const satellite = L.tileLayer(
 | |
|         mapboxSat,
 | |
|         {
 | |
|             id: 'mapbox.satellite',
 | |
|             tileSize: 512,
 | |
|             zoomOffset: -1,
 | |
|             attribution: mapboxAttribution,
 | |
|             maxZoom: 18
 | |
|         }
 | |
|     );
 | |
|     const baseMaps = {
 | |
|         "OpenStreetMap": streets,
 | |
|         "Mapbox Satellite": satellite
 | |
|     };
 | |
|     const map = L.map(htmlId, {
 | |
|         center: centerCoords,
 | |
|         zoom: 18,
 | |
|         layers: [streets, satellite]
 | |
|     });
 | |
| 
 | |
|     L.control.layers(baseMaps).addTo(map);
 | |
| 
 | |
|     const geoLayer = L.geoJSON().addTo(map);
 | |
|     geoLayer.addData(geoJSON);
 | |
| 
 | |
|     return centerCoords;
 | |
|     /*
 | |
|     L.marker(coordinates).addTo(map)
 | |
|         .bindPopup(`lat.: ${coordinates[0]}, long. : ${coordinates[1]}`);
 | |
|     */
 | |
| }
 | |
| /**
 | |
|  * @todo Use TS to define object shape
 | |
|  * @param {object} resource The resource object (Arches JSON!)
 | |
|  *
 | |
|  * @return {string[]}
 | |
|  */
 | |
| DataSpace.getImagesSrc = function (resource) {
 | |
|     // TODO don't filter this array, populate another one
 | |
|     let arr = resource.tiles
 | |
|         .filter(tile => { 
 | |
|             let key = Object.keys(tile.data)[0];
 | |
|             return Array.isArray(tile.data[key]);
 | |
|         }).filter(o => {
 | |
|             let key = Object.keys(o.data)[0];
 | |
|             return Object.keys(o.data[key][0]).includes('file_id');
 | |
|         });
 | |
| 
 | |
|     let fileNames = [],
 | |
|         dataObjects = [];
 | |
| 
 | |
|     arr.forEach(d => dataObjects.push(d.data));
 | |
| 
 | |
|     dataObjects.forEach(e => {
 | |
|         e[Object.keys(e)[0]].forEach(o => {
 | |
|             fileNames.push(this.FILES_URI + o.name)
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     return fileNames;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @todo The order of elements in the tiles array
 | |
|  *       in arches-json is the same as that of
 | |
|  *       objects in the JSON resource (report)
 | |
|  *
 | |
|  * @param {object} resource The resource object (Arches JSON!)
 | |
|  *
 | |
|  * @return {string[]}
 | |
| DataSpace.getLinkedData = function (resource) 
 | |
| {
 | |
| }
 | |
| */
 | |
| 
 | |
| function _fetchError(message, htmlId)
 | |
| {
 | |
|     const error = document.createElement('div');
 | |
|     const clear = document.createElement('button');
 | |
|     error.className = 'toast toast-error';
 | |
|     clear.className = 'btn btn-clear float-right';
 | |
| 
 | |
|     error.appendChild(clear);
 | |
|     error.textContent = message;
 | |
| 
 | |
|     document.querySelector(`#${htmlId}`).appendChild(error);
 | |
| }
 | |
| 
 | |
| function _createImgGallery(images, htmlId)
 | |
| {
 | |
|     let gallery = document.querySelector(`#${htmlId}`);
 | |
|     gallery.parentElement
 | |
|         .classList.remove('d-hide');
 | |
| 
 | |
|     for (const src of images) {
 | |
|         const img = document.createElement('img');
 | |
|         img.className = 'img-responsive img-fit-cover';
 | |
|         img.src = src;
 | |
| 
 | |
|         const col = document.createElement('div');
 | |
|         col.className = 'column col-3 c-hand spotlight';
 | |
|         col.setAttribute('data-src', src);
 | |
|         col.setAttribute('data-download', true);
 | |
| 
 | |
|         col.appendChild(img);
 | |
|         gallery.appendChild(col);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function _createReportTable(resType, shape, resource)
 | |
| {
 | |
|     const tableElement = document.querySelector('#res-before tbody');
 | |
|     for (const key in shape.get('before-gallery')) {
 | |
|         const row = document.createElement('tr');
 | |
|         let innerList = null;
 | |
|         
 | |
|         // TODO refactor
 | |
|         if (typeof resource[key] == 'object') {
 | |
|             innerList = document.createElement('ul');
 | |
| 
 | |
|             for (const innerKey in resource[key]) {
 | |
|                 const li = document.createElement('li');
 | |
| 
 | |
|                 li.innerHTML = innerKey === '@value' ?
 | |
|                     resource[key]['@value'] :
 | |
|                     `<strong>${innerKey.replace(key,'')}</strong>:
 | |
|                     ${resource[key][innerKey]}`;
 | |
| 
 | |
|                 if (resource[key][innerKey] !== null) {
 | |
|                     innerList.appendChild(li);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let value = innerList !== null ? 
 | |
|             innerList.outerHTML : resource[key];
 | |
| 
 | |
|         row.innerHTML = `
 | |
|             <td class="text-bold key">${key.replace(resType, '')}</td>
 | |
|             <td>${value}</td>
 | |
|         `;
 | |
| 
 | |
|         if (!key.includes('Images') && !key.includes('Photos')) {
 | |
|             tableElement.appendChild(row);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // TODO `Read more` for description
 | |
| function _createReportTail(resType, shape, resource)
 | |
| {
 | |
|     let after = document.querySelector('#res-after');
 | |
| 
 | |
|     for (const key in shape.get('after-gallery-1-col')) {
 | |
|         const col = document.createElement('div');
 | |
| 
 | |
|         col.className = 'column col-12';
 | |
|         col.innerHTML = `
 | |
|             <p class="text-bold">${key.replace(resType, '')}</p>
 | |
|             <p>${resource[key]}</p>
 | |
|         `;
 | |
| 
 | |
|         after.appendChild(col);
 | |
|     }
 | |
| 
 | |
|     for (const key in shape.get('after-gallery-2-col')) {
 | |
|         const col = document.createElement('div');
 | |
| 
 | |
|         col.className = 'column col-6';
 | |
|         let displayValue = resource[key];
 | |
|         const isNested = typeof resource[key] === 'object';
 | |
| 
 | |
|         if (isNested) {
 | |
|             for (const innerKey in resource[key]) {
 | |
|                 if (resource[key][innerKey] !== "") {
 | |
|                     const innerCol = document.createElement('div');
 | |
|                     innerCol.className = 'column col-6';
 | |
|                     innerCol.innerHTML = innerKey === '@value' ?
 | |
|                         `
 | |
|                             <p class="text-bold">${key.replace(resType, '')}</p>
 | |
|                             <p>${displayValue['@value'].replace('False', 'No')
 | |
|                                 .replace('True', 'Yes')}</p>
 | |
|                         ` :
 | |
|                         `
 | |
|                             <p class="text-bold">${innerKey.replace(resType, '')}</p>
 | |
|                             <p>${displayValue[innerKey]}</p>
 | |
|                         ` ;
 | |
| 
 | |
|                     after.appendChild(innerCol);
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             col.innerHTML = `
 | |
|                 <p class="text-bold">${key.replace(resType, '')}</p>
 | |
|                 <p>${displayValue}</p>
 | |
|             `;
 | |
| 
 | |
|              after.appendChild(col); 
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| export default DataSpace;
 |