diff --git a/grails-app/assets/javascripts/entities.js b/grails-app/assets/javascripts/entities.js index 7cbba7c..9aca165 100644 --- a/grails-app/assets/javascripts/entities.js +++ b/grails-app/assets/javascripts/entities.js @@ -861,7 +861,7 @@ function getDB() { var DB_NAME = "biocollect"; var db = new Dexie(DB_NAME); - db.version(4).stores({ + db.version(5).stores({ taxon: ` ++id, projectActivityId, diff --git a/grails-app/assets/javascripts/images.js b/grails-app/assets/javascripts/images.js index 5a1ce6c..b9b265c 100644 --- a/grails-app/assets/javascripts/images.js +++ b/grails-app/assets/javascripts/images.js @@ -4,7 +4,12 @@ function ImageViewModel(prop, skipFindingDocument, context){ // used by image gallery plugin. document is passed to the function. if(!skipFindingDocument){ - documents = context.documents; + documents = context && context.documents; + // added from biocollect images.js + if (!documents && window.activityLevelData) { + documents = activityLevelData.activity.documents; + } + // dereferencing the document using documentId documents && documents.forEach(function(doc){ // newer implementation is passing document object. @@ -24,7 +29,7 @@ function ImageViewModel(prop, skipFindingDocument, context){ self.contentType = ko.observable(prop.contentType); self.url = ko.observable(prop.url); self.filesize = prop.filesize; - self.thumbnailUrl = ko.observable(prop.thumbnailUrl); + self.thumbnailUrl = ko.observable(prop.thumbnailUrl || prop.url); self.filename = prop.filename; self.attribution = ko.observable(prop.attribution); self.licence = ko.observable(prop.licence); @@ -42,48 +47,121 @@ function ImageViewModel(prop, skipFindingDocument, context){ self.isEmbargoed = prop.isEmbargoed; self.identifier=prop.identifier; self.blob = undefined; + // adds event methods like on, emit etc. + if (window.Emitter) { + new Emitter(self); + } self.remove = function(images, data, event){ if(data.documentId){ // change status when image is already in ecodata - data.status('deleted') + data.status('deleted'); + // code for pwa app + if (window.entities && window.entities.utils.isDexieEntityId(data.documentId)) { + entities + .bulkDeleteDocuments([data.documentId]) + .then(function (){ + images.remove(data); + }); + } } else { images.remove(data); } } + self.getActivityLink = function(){ + return fcConfig.activityViewUrl + '/' + self.activityId; + } + + self.getProjectLink = function(){ + return fcConfig.projectIndexUrl + '/' + self.projectId; + } + + self.getImageViewerUrl = function(){ + // Let the image viewer render high res image. + var url = self.url() ? self.url().split("/image/proxyImageThumbnailLarge?imageId=").join("/image/proxyImage?imageId=") : self.url() + self.url(url); + return fcConfig.imageLeafletViewer + '?file=' + encodeURIComponent(self.url()); + } + + self.summary = function(){ + var picBy = 'Picture by ' + self.attribution() + '. '; + var takenOn = 'Taken on ' + self.dateTaken.formattedDate() +'.'; + var message = ''; + if(self.attribution()){ + message += picBy; + } + + message += takenOn; + return "
" + self.notes() + '
' + message + ''; + } + + self.load = function(prop, doNotUpdateUrls){ + self.dateTaken(prop.dateTaken || (new Date()).toISOStringNoMillis()); + self.contentType(prop.contentType); + if (!doNotUpdateUrls) { + self.url(prop.url); + self.thumbnailUrl(prop.thumbnailUrl || prop.url); + } + + self.filename = prop.filename; + prop.attribution && self.attribution(prop.attribution); + prop.licence && self.licence(prop.licence); + prop.notes && self.notes(prop.notes || ''); + prop.name && self.name(prop.name); + prop.status && self.status(prop.status || 'active'); + if(prop.filesize) + self.filesize = prop.filesize + if(prop.licenceDescription) + self.licenceDescription = prop.licenceDescription; + if(prop.filesize) + self.formattedSize = formatBytes(prop.filesize); + if(prop.staged !== undefined) + self.staged = prop.staged || false; + if(prop.documentId) + self.documentId = prop.documentId || ''; + if(prop.projectName) + self.projectName = prop.projectName; + if(prop.projectId) + self.projectId = prop.projectId; + if(prop.activityName) + self.activityName = prop.activityName; + if(prop.activityId) + self.activityId = prop.activityId; + if(prop.isEmbargoed) + self.isEmbargoed = prop.isEmbargoed; + if(prop.identifier) + self.identifier = prop.identifier; + } + /** * any document that is in index db. Their url will be prefixed with blob:. */ self.isBlobDocument = function(){ - return !!document.blob; + return !!(document && !!document.blob); } self.getBlob = function(){ - return document.blobObject; + return document && document.blobObject; } self.isBlobUrl = function(url){ return url && url.indexOf('blob:') === 0; } - self.getActivityLink = function(){ - return fcConfig.activityViewUrl + '/' + self.activityId; - } - - self.getProjectLink = function(){ - return fcConfig.projectIndexUrl + '/' + self.projectId; - } - - self.getImageViewerUrl = function(){ - return fcConfig.imageLeafletViewer + '?file=' + encodeURIComponent(self.url()); + self.getDocument = function() { + return document } /** * Check if the url is a valid object url. */ self.fetchImage = function() { + // making sure calling this function does not fail in MERIT + if (!window.isUuid || !window.entities ) + return; + if (!isUuid(self.documentId) && !isNaN(self.documentId)) { var documentId = parseInt(self.documentId); entities.offlineGetDocument(documentId).then(function(result) { @@ -99,25 +177,16 @@ function ImageViewModel(prop, skipFindingDocument, context){ self.url(url); self.thumbnailUrl(url); } - }); - } - } - self.summary = function(){ - var picBy = 'Picture by ' + self.attribution() + '. '; - var takenOn = 'Taken on ' + self.dateTaken.formattedDate() +'.'; - var message = ''; - if(self.attribution()){ - message += picBy; + document && self.emit('image-fetched', document); + }); } - - message += takenOn; - return "" + self.notes() + '
' + message + ''; } self.fetchImage(); } + ImageViewModel.createObjectURL = function addObjectURL(document){ if (document.blob) { var blob = document.blobObject = new Blob([document.blob], {type: document.contentType}); diff --git a/grails-app/assets/javascripts/viewModels.js b/grails-app/assets/javascripts/viewModels.js index 89333f2..d53e40f 100644 --- a/grails-app/assets/javascripts/viewModels.js +++ b/grails-app/assets/javascripts/viewModels.js @@ -22,7 +22,8 @@ function enmapify(args) { "use strict"; - var SITE_CREATE = 'sitecreate', SITE_PICK = 'sitepick', SITE_PICK_CREATE = 'sitepickcreate'; + var SITE_CREATE = 'sitecreate', SITE_PICK = 'sitepick', SITE_PICK_CREATE = 'sitepickcreate', + SITE_NAME='Location of the sighting'; var viewModel = args.viewModel, container = args.container, validationContainer = args.validationContainer || '#validation-container', @@ -493,6 +494,7 @@ function enmapify(args) { * @param siteId */ function updateMapForSite(siteId) { + console.trace(`Updating map for site ${siteId}`); if (typeof siteId !== "undefined" && siteId) { if (lonObservable()) { previousLonObservable(lonObservable()); @@ -506,21 +508,8 @@ function enmapify(args) { return siteId == site.siteId })[0]; //search from site collection in case it is a private site - if (!matchingSite){ - fetchSite(siteId).done(function (result) { - if (result.data) { - var site = result.data; - site.name='Location of the sighting'; - sitesObservable.push(site); - matchingSite = site; - map.clearBoundLimits(); - map.setGeoJSON(Biocollect.MapUtilities.featureToValidGeoJson(matchingSite.extent.geometry)); - // Reassign since siteIdObservable value is cleared when the site is not listed in sitesObservable. - siteIdObservable(siteId); - } - }).fail(function(result) { - console.log(result.message); - }); + if (!matchingSite) { + offlineGetSiteAndAddToSiteList(siteId) } // TODO: OPTIMISE THE PROCEDUE @@ -544,6 +533,53 @@ function enmapify(args) { } } + function offlineGetSiteAndAddToSiteList(siteId) { + addFakeSiteObject(siteId); + offlineFetchSite(siteId).then(function (result) { + var site = result.data; + if (site) { + site.name = site.name || SITE_NAME; + sitesObservable.push(site); + nameObservable(site.name); + map.clearBoundLimits(); + map.setGeoJSON(Biocollect.MapUtilities.featureToValidGeoJson(site.extent.geometry)); + + subscribeOrDisposeSiteIdObservable(false); + siteIdObservable(siteId); + removeFakeSiteObject(siteId); + subscribeOrDisposeSiteIdObservable(true); + } + }); + } + + /** + * Adding fake object so that knockout select binding does not rewrite siteIdObservable due to it not finding + * site object in sitesObservable. + * @param siteId + */ + function addFakeSiteObject (siteId) { + sitesObservable.push({ + siteId: siteId, + name: SITE_NAME, + fakeObject: true, + extent:{ + geometry: { + type: ALA.MapConstants.DRAW_TYPE.POINT_TYPE + } + } + }); + } + + function removeFakeSiteObject (siteId) { + var sites = $.grep(sitesObservable(), function (site) { + return (siteId == site.siteId) && (site.fakeObject === true); + }); + + sites.forEach(function (site) { + sitesObservable.remove(site); + }); + } + function fetchSite(siteId) { if (enableOffline) { if (isUuid(siteId)) { @@ -1087,6 +1123,34 @@ function enmapify(args) { return type; } + function mergeSitesObservable(sites) { + var originalSites = sitesObservable(), + updatedSites = []; + + for (var i = 0; i < sites.length; i++) { + var site = sites[i], originalIndex; + var originalSite = $.grep(originalSites, function (s, index) { + if (s.siteId === site.siteId){ + originalIndex = index; + return true; + } + })[0]; + + if (originalSite) { + updatedSites.push(site); + originalSites.splice(originalIndex, 1); + } else { + updatedSites.push(site); + } + } + + if (originalSites.length > 0) { + updatedSites.push.apply(updatedSites, originalSites); + } + + sitesObservable(updatedSites); + } + function reloadSiteData() { if (enableOffline) { return isOffline().then(function () { @@ -1103,7 +1167,7 @@ function enmapify(args) { function onlineReloadSiteData() { var entityType = activityLevelData.pActivity.projectActivityId ? "projectActivity" : "project" return $.getJSON(listSitesUrl + '/' + (activityLevelData.pActivity.projectActivityId || activityLevelData.pActivity.projectId) + "?entityType=" + entityType).then(function (data, textStatus, jqXHR) { - sitesObservable(data); + mergeSitesObservable(data); }); } @@ -1118,7 +1182,7 @@ function enmapify(args) { if (window.entities) { entities.offlineGetSites(siteIds).then(function (result) { var sites = result.data; - sitesObservable(sites); + mergeSitesObservable(sites); deferred.resolve({message: "Sites retrieved from db.", success: true, data: sites}); }, deferred.reject); } @@ -1129,7 +1193,7 @@ function enmapify(args) { case "project": if (window.entities) { entities.offlineGetSitesForProject(projectId).then(function (sites) { - sitesObservable(sites); + mergeSitesObservable(sites); deferred.resolve({message: "Sites retrieved from db.", success: true, data: sites}); }, deferred.reject); } diff --git a/grails-app/views/output/_imageDataTypeViewModelTemplate.gsp b/grails-app/views/output/_imageDataTypeViewModelTemplate.gsp index 3b9fe61..e340a5b 100644 --- a/grails-app/views/output/_imageDataTypeViewModelTemplate.gsp +++ b/grails-app/views/output/_imageDataTypeViewModelTemplate.gsp @@ -2,7 +2,7 @@