From 52c3f9222c3355b802583e31b93e147b4d48b116 Mon Sep 17 00:00:00 2001 From: Ralf Hauser Date: Sun, 23 Feb 2020 15:52:54 +0100 Subject: [PATCH] https://github.com/water-fountains/datablue/issues/41 now also lazy loading categories if featured image is available --- server/api/controllers/controller.js | 88 +++++-- server/api/services/wikimedia.service.js | 306 ++++++++++++----------- server/common/constants.js | 3 +- 3 files changed, 226 insertions(+), 171 deletions(-) diff --git a/server/api/controllers/controller.js b/server/api/controllers/controller.js index d7639d21..e7a5f3a1 100644 --- a/server/api/controllers/controller.js +++ b/server/api/controllers/controller.js @@ -20,9 +20,10 @@ import { } from "../services/processing.service"; import {updateCacheWithFountain} from "../services/database.service"; import {extractProcessingErrors} from "./processing-errors.controller"; -import {getImageInfo} from "../services/wikimedia.service"; +import {getImageInfo,getImgsOfCat} from "../services/wikimedia.service"; const haversine = require("haversine"); const _ = require('lodash'); +import {MAX_IMG_SHOWN_IN_GALLERY} from "../../common/constants"; // Configuration of Cache after https://www.npmjs.com/package/node-cache @@ -265,34 +266,69 @@ function byId(req, res, dbg){ let i = 0; let lzAtt = ''; const showDetails = true; - for(const img of gal.value) { - let imMetaDat = img.metadata; - if (null == imMetaDat) { - lzAtt += i+','; - l.info('controller.js byId lazy getImageInfo: '+cityS+' '+i+'/'+gl+' "'+img.pgTit+'" "'+name+'" '+dbg+' '+new Date().toISOString()); - imgMetaPromises.push(getImageInfo(img, i+'/'+gl+' '+dbg+' '+name+' '+cityS,showDetails, new Map()).catch(giiErr=>{ - l.info('wikimedia.service.js: fillGallery getImageInfo failed for "'+img.pgTit+'" '+dbg+' '+city+' '+dbgIdWd+' "'+name+'" '+new Date().toISOString() - + '\n'+giiErr.stack); - })); - lazyAdded++; - } else { -// l.info('controller.js byId: of '+cityS+' found imMetaDat '+i+' in gal of size '+gl+' "'+name+'" '+dbg+' '+new Date().toISOString()); + let imgUrlSet = new Set(); + let catPromises = []; + let numbOfCats = -1; + let numbOfCatsLazyAdded = 0; + let imgUrlsLazyByCat = []; + if (props.wiki_commons_name && props.wiki_commons_name.value && 0 < props.wiki_commons_name.value.length) { + numbOfCats = props.wiki_commons_name.value.length; + for(const cat of props.wiki_commons_name.value) { + const add = 0 > cat.l; + if (add) { + numbOfCatsLazyAdded++; + if (0 == imgUrlSet.size) { + for(const img of gal.value) { + imgUrlSet.add(img.pgTit); + } + } + const catPromise = getImgsOfCat(cat, dbg, cityS, imgUrlSet, imgUrlsLazyByCat, "dbgIdWd", props,true); + //TODO we might prioritize categories with small number of images to have greater variety of images? + catPromises.push(catPromise); + } + } + } + Promise.all(catPromises).then(r => { + for(let k = 0; k < imgUrlsLazyByCat.length && k < MAX_IMG_SHOWN_IN_GALLERY;k++) { //between 6 && 50 imgs are on the gallery-preview + const img = imgUrlsLazyByCat[k]; + let nImg = {s: img.src,pgTit: img.val,c: img.cat}; + gal.value.push(nImg); + } + if (0 < imgUrlsLazyByCat.length) { + l.info('controller.js byId lazy img by lazy cat added: attempted '+imgUrlsLazyByCat.length+' in '+numbOfCatsLazyAdded+'/'+ + numbOfCats+' cats, tot '+gl+' of '+cityS+' '+dbg+' "'+name+'" '+r.length+' '+new Date().toISOString()); + } + for(const img of gal.value) { + let imMetaDat = img.metadata; + if (null == imMetaDat) { + lzAtt += i+','; + l.info('controller.js byId lazy getImageInfo: '+cityS+' '+i+'/'+gl+' "'+img.pgTit+'" "'+name+'" '+dbg+' '+new Date().toISOString()); + imgMetaPromises.push(getImageInfo(img, i+'/'+gl+' '+dbg+' '+name+' '+cityS,showDetails, new Map()).catch(giiErr=>{ + l.info('wikimedia.service.js: fillGallery getImageInfo failed for "'+img.pgTit+'" '+dbg+' '+city+' '+dbgIdWd+' "'+name+'" '+new Date().toISOString() + + '\n'+giiErr.stack); + })); + lazyAdded++; + } else { +// l.info('controller.js byId: of '+cityS+' found imMetaDat '+i+' in gal of size '+gl+' "'+name+'" '+dbg+' '+new Date().toISOString()); + } + i++; } - i++; - } - if (0 < lazyAdded) { - l.info('controller.js byId lazy img metadata loading: attempted '+lazyAdded+'/'+gl+' ('+lzAtt+') of '+cityS+' '+dbg+' "'+name+'" '+new Date().toISOString()); - } - Promise.all(imgMetaPromises).then(r => { if (0 < lazyAdded) { - l.info('controller.js byId lazy img metadata loading after promise: attempted '+lazyAdded+' tot '+gl+' of '+cityS+' '+dbg+' "'+name+'" '+r.length+' '+new Date().toISOString()); + l.info('controller.js byId lazy img metadata loading: attempted '+lazyAdded+'/'+gl+' ('+lzAtt+') of '+cityS+' '+dbg+' "'+name+'" '+new Date().toISOString()); } - doJson(res,fountain, 'byId '+dbg); // res.json(fountain); - l.info('controller.js byId: of '+cityS+' res.json '+dbg+' "'+name+'" '+new Date().toISOString()); - resolve(fountain); - }, err => { - l.error(`controller.js: Failed on imgMetaPromises: ${err.stack} .`+dbg+' "'+name+'" '+cityS); - }); + Promise.all(imgMetaPromises).then(r => { + if (0 < lazyAdded) { + l.info('controller.js byId lazy img metadata loading after promise: attempted '+lazyAdded+' tot '+gl+' of '+cityS+' '+dbg+' "'+name+'" '+r.length+' '+new Date().toISOString()); + } + doJson(res,fountain, 'byId '+dbg); // res.json(fountain); + l.info('controller.js byId: of '+cityS+' res.json '+dbg+' "'+name+'" '+new Date().toISOString()); + resolve(fountain); + }, err => { + l.error(`controller.js: Failed on imgMetaPromises: ${err.stack} .`+dbg+' "'+name+'" '+cityS); + }); + }, err => { + l.error(`controller.js: Failed on imgMetaPromises: ${err.stack} .`+dbg+' "'+name+'" '+cityS); + }); } else { l.info('controller.js byId: of '+cityS+' gl < 1 '+dbg+' '+new Date().toISOString()); doJson(res,fountain, 'byId '+dbg); diff --git a/server/api/services/wikimedia.service.js b/server/api/services/wikimedia.service.js index 2e166eb1..b16f4585 100644 --- a/server/api/services/wikimedia.service.js +++ b/server/api/services/wikimedia.service.js @@ -13,7 +13,8 @@ const axios = require ('axios'); const { ConcurrencyManager } = require("axios-concurrency"); const md5 = require('js-md5'); import l from '../../common/logger'; -import {PROP_STATUS_ERROR, PROP_STATUS_INFO, PROP_STATUS_OK, PROP_STATUS_WARNING} from "../../common/constants"; +import {PROP_STATUS_ERROR, PROP_STATUS_INFO, PROP_STATUS_OK, PROP_STATUS_WARNING, + MAX_IMG_SHOWN_IN_GALLERY} from "../../common/constants"; let api = axios.create({}); @@ -26,59 +27,6 @@ const manager = ConcurrencyManager(api, MAX_CONCURRENT_REQUESTS); class WikimediaService { - addToImgList(imgListWithSource, imgUrlSet, imgUrls, dbg, debugAll, cat) { - let i = -1; - if (null != imgListWithSource && null != imgListWithSource.imgs) { - i++; - let duplicateCount = 0; - for(let foFeaImg of imgListWithSource.imgs) { - let imgNam = foFeaImg.value; - if (null == foFeaImg.typ) { - foFeaImg.typ = imgListWithSource.type; - } - let pTit = imgNam.toLowerCase(); - let dotPos = pTit.lastIndexOf("."); - // only use photo media, not videos - let ext = pTit.substring(dotPos+1); - if(['jpg','jpeg', 'png', 'gif','tif','tiff','svg','ogv', 'webm'].indexOf(ext)<0){ - l.info('wikimedia.service.js addToImgList '+ext+': skipping "'+page.title+'" '+dbgImg+' '+dbgIdWd+' '+city); - //https://github.com/lukasz-galka/ngx-gallery/issues/296 to handle svg, ogv, webm - continue; - } - if ('wm'==foFeaImg.typ) { - //if ('wd'==imgListWithSource.src ||'osm'==imgListWithSource.src) { - if (!imgUrlSet.has(imgNam)) { - imgUrlSet.add(imgNam); - let img = { - src: imgListWithSource.src, - val: foFeaImg.value, - typ:'wm', - cat: cat - } - imgUrls.push(img); - i++; - } else { - if (debugAll) { - l.info('wikimedia.service.js addToImgList foFeaImg: duplicate "'+imgNam+'" ' +i + '/' +imgListWithSource.imgs.length + ' - ' +dbg + ' '+new Date().toISOString()); - } - duplicateCount++; - } - } else { - l.info('wikimedia.service.js addToImgList foFeaImg: unknown src "'+imgListWithSource.src+'" "'+imgNam+'" ' +dbg + ' '+new Date().toISOString()); - } - }; - if (0 < duplicateCount && debugAll - ) { - l.info('wikimedia.service.js addToImgList foFeaImg: '+duplicateCount+' duplicates found among ' +imgListWithSource.imgs.length + ' - ' +dbg + ' '+new Date().toISOString()); - } - if (1 < imgListWithSource.imgs.length && debugAll) { - if(process.env.NODE_ENV !== 'production') { - l.info('wikimedia.service.js addToImgList foFeaImg: added '+imgListWithSource.imgs.length+' ' +dbg); - } - } - } - return i; - } getName(f) { if(f.properties.name.value === null){ @@ -91,62 +39,28 @@ class WikimediaService { } return f.properties.name.value; } - - getImgsOfCat(cat, dbg, city, imgUrlSet, imgUrls, dbgIdWd, fProps, debugAll) { - let catName = cat.c; - // if there is a gallery, then fetch all images in category - const imgsPerCat = 20; - let url = `https://commons.wikimedia.org/w/api.php?action=query&list=categorymembers&cmtype=file&cmlimit=${imgsPerCat}&cmtitle=Category:${this.sanitizeTitle(encodeURIComponent(catName))}&prop=imageinfo&format=json`; - // make array of image promises - let imgValsCumul = []; - let imgNoInfoPomise = api.get(url, {timeout: 1000}) - .then(r => { - let category_members = r.data.query['categorymembers']; - let cI = 0; - cat.l = category_members.length; - if(process.env.NODE_ENV !== 'production' && debugAll) { - l.info('wikimedia.service.js getImgsOfCat: category "'+catName+'" has '+cat.l+' (limit '+imgsPerCat+') images '+dbg+' '+city+' '+ - dbgIdWd+' '+new Date().toISOString()); - } - // fetch information for each image, max 50 - for(; cI < cat.l && cI < 50;cI++) { - let page = category_members[cI]; - let dbgImg = "f-"+dbg+"_i-"+cI+"/"+cat.l; - let imgLikeFromWikiMedia = { - value: page.title.replace('File:',''), - typ:'wm' - } - let imgVals = []; - imgVals.push(imgLikeFromWikiMedia); - imgValsCumul.push(imgLikeFromWikiMedia); - let imgs = { src: 'wd', - imgs: imgVals, - type:'wm' }; - let addedC = this.addToImgList(imgs, imgUrlSet, imgUrls, dbg + ' '+ dbgIdWd+' cat "'+catName+'"', - debugAll,{n:catName,l:cat.l}); - }; - return Promise.all(imgValsCumul); - }).catch(err=> { - // If there is an error getting the category members, then reject with error - l.error('getImgsOfCat.categorymembers = api.get:\n'+ - `Failed to fetch category members. Cat "`+catName+'" ' +dbg + ' '+ dbgIdWd - + ' url '+url+'\n'+err.stack); - // add gallery as value of fountain gallery property - fProps.gallery.issues.push({ - data: err, - context: { - fountain_name: fProps.name.value, - property_id: 'gallery', - id_osm: fProps.id_osm.value, - id_wikidata: fProps.id_wikidata.value - }, - timeStamp: new Date(), - type: 'data_processing', - level: 'error', - message: `Failed to fetch category members from Wikimedia Commons. Url: ${url} `+dbg, - }); - }); - return imgNoInfoPomise; + + getImgsFromCats(fProps, dbg,city,dbgIdWd,name,imgNoInfoPomises, imgUrlSet, imgUrls,debugAll) { + let catNames = fProps.wiki_commons_name.value; + if (1 < catNames.length) { + if (debugAll || 2 < catNames.length) { + l.info('wikimedia.service.js: '+catNames.length+' commons categories defined "'+dbg+' ' + +city+' '+dbgIdWd+' "'+name+'" '+new Date().toISOString()); + } + } + let catName = 'unkCatNam'; + for(let i = 0;i < catNames.length; i++) { + const cat = catNames[i]; + catName = cat.c; + if (65 == i) { + l.info('wikimedia.service.js: '+catNames.length+' commons categories defined "'+dbg+' ' + +city+' '+dbgIdWd+' "'+name+'" '+new Date().toISOString()); + } +// lastCatName = catName; + const imgNoInfoPomise = getImgsOfCat(cat, dbg, city, imgUrlSet, imgUrls, dbgIdWd, fProps,debugAll); + //TODO we might prioritize categories with small number of images to have greater variety of images? + imgNoInfoPomises.push(imgNoInfoPomise); + } } fillGallery(fountain, dbg, city, debugAll, allMap){ @@ -184,7 +98,7 @@ class WikimediaService { let imgUrlSet = new Set(); let foFeaImgs = fProps.featured_image_name; let foFeaImgsV = foFeaImgs.value; - let added = this.addToImgList(foFeaImgsV, imgUrlSet, imgUrls, dbg + ' '+ dbgIdWd, debugAll,{n:'wd:p18'}); + let added = addToImgList(foFeaImgsV, imgUrlSet, imgUrls, dbg + ' '+ dbgIdWd, debugAll,{n:'wd:p18'}); let imgNoInfoPomises = []; let noCats = _.isNull(fProps.wiki_commons_name.value) || 0 == fProps.wiki_commons_name.value.length; if(noCats) { @@ -193,23 +107,15 @@ class WikimediaService { l.info('wikimedia.service.js: no commons category defined "'+dbg+' '+city+' '+dbgIdWd+' '+new Date().toISOString()); } imgNoInfoPomises.push(new Promise((resolve, reject)=>resolve(false))); - }else{ - let catNames = fProps.wiki_commons_name.value; - if (1 < catNames.length) { - if (debugAll || 2 < catNames.length) { - l.info('wikimedia.service.js: '+catNames.length+' commons categories defined "'+dbg+' ' - +city+' '+dbgIdWd+' "'+name+'" '+new Date().toISOString()); - } - } - let catName = 'unkCatNam'; - for(let i = 0;i < catNames.length; i++) { - const cat = catNames[i]; - catName = cat.c; - lastCatName = catName; - const imgNoInfoPomise = this.getImgsOfCat(cat, dbg, city, imgUrlSet, imgUrls, dbgIdWd, fProps,debugAll); - //TODO we might prioritize categories with small number of images to have greater variety of images? - imgNoInfoPomises.push(imgNoInfoPomise); - } + } else { + if (0 < imgUrls.length) { + if(process.env.NODE_ENV !== 'production' && debugAll) { + l.info('wikimedia.service.js: lazyLoad no need to analyze commons category now (already '+imgUrls.length+' featured img) "'+dbg+' '+city+' '+dbgIdWd+' '+new Date().toISOString()); + } + imgNoInfoPomises.push(new Promise((resolve, reject)=>resolve(false))); + } else { + this.getImgsFromCats(fProps, dbg,city,dbgIdWd,name,imgNoInfoPomises, imgUrlSet, imgUrls,debugAll); + } } Promise.all(imgNoInfoPomises) .then(cr => { @@ -218,9 +124,8 @@ class WikimediaService { if (debugAll) { l.info('wikimedia.service.js: fillGallery imgUrlSet.size '+totImgFound+' "'+dbg+' '+city+' '+dbgIdWd+' '+new Date().toISOString()); } - const maxImgShown = 50; - if (maxImgShown < totImgFound) { - l.info('wikimedia.service.js: fillGallery only showing first '+maxImgShown+' out of imgUrlSet.size '+totImgFound+' "'+dbg+'" '+city+' '+dbgIdWd+' '+new Date().toISOString()); + if (MAX_IMG_SHOWN_IN_GALLERY < totImgFound) { + l.info('wikimedia.service.js: fillGallery only showing first '+MAX_IMG_SHOWN_IN_GALLERY+' out of imgUrlSet.size '+totImgFound+' "'+dbg+'" '+city+' '+dbgIdWd+' '+new Date().toISOString()); } let galValPromises = []; let k = 0; @@ -247,7 +152,7 @@ class WikimediaService { ); } } - for(;maxImgPreFetched <= k && k < imgL && k < maxImgShown;k++) { //between 6 && 50 imgs are on the gallery-preview + for(;maxImgPreFetched <= k && k < imgL && k < MAX_IMG_SHOWN_IN_GALLERY;k++) { //between 6 && 50 imgs are on the gallery-preview const img = imgUrls[k]; let nImg = {s: img.src,pgTit: img.val,c: img.cat}; galValPromises.push(nImg); @@ -285,7 +190,7 @@ class WikimediaService { fountain.properties.gallery.status = PROP_STATUS_OK; fountain.properties.gallery.comments = ''; fountain.properties.gallery.source = 'wiCommns'; - fountain.properties.gallery.totImgs = totImgFound; //TODO display in GUI if > maxImgShown + fountain.properties.gallery.totImgs = totImgFound; //TODO display in GUI if > MAX_IMG_SHOWN_IN_GALLERY resolve(fountain); }); } else { @@ -322,17 +227,7 @@ class WikimediaService { }) ; } - - sanitizeTitle(title){ - // this doesn't cover all situations, but the following doesn't work either - // return encodeURI(title.replace(/ /g, '_')); - return title - .replace(/ /g, '_') - .replace(/,/g, '%2C') - // .replace(/ü/g, '%C3%BC') - .replace(/&/g, '%26'); - } - + } function makeMetadata(data){ @@ -369,6 +264,71 @@ function makeMetadata(data){ return metadata; } +function sanitizeTitle(title){ + // this doesn't cover all situations, but the following doesn't work either + // return encodeURI(title.replace(/ /g, '_')); + return title + .replace(/ /g, '_') + .replace(/,/g, '%2C') + // .replace(/ü/g, '%C3%BC') + .replace(/&/g, '%26'); + } + +function addToImgList(imgListWithSource, imgUrlSet, imgUrls, dbg, debugAll, cat) { + let i = -1; + if (null != imgListWithSource && null != imgListWithSource.imgs) { + i++; + let duplicateCount = 0; + for(let foFeaImg of imgListWithSource.imgs) { + let imgNam = foFeaImg.value; + if (null == foFeaImg.typ) { + foFeaImg.typ = imgListWithSource.type; + } + let pTit = imgNam.toLowerCase(); + let dotPos = pTit.lastIndexOf("."); + // only use photo media, not videos + let ext = pTit.substring(dotPos+1); + if(['jpg','jpeg', 'png', 'gif','tif','tiff','svg','ogv', 'webm'].indexOf(ext)<0){ + l.info('wikimedia.service.js addToImgList '+ext+': skipping "'+page.title+'" '+dbgImg+' '+dbgIdWd+' '+city); + //https://github.com/lukasz-galka/ngx-gallery/issues/296 to handle svg, ogv, webm + continue; + } + if ('wm'==foFeaImg.typ) { + //if ('wd'==imgListWithSource.src ||'osm'==imgListWithSource.src) { + if (!imgUrlSet.has(imgNam)) { + imgUrlSet.add(imgNam); + let img = { + src: imgListWithSource.src, + val: foFeaImg.value, + typ:'wm', + cat: cat + } + imgUrls.push(img); + i++; + } else { + if (debugAll) { + l.info('wikimedia.service.js addToImgList foFeaImg: duplicate "'+imgNam+'" ' +i + '/' +imgListWithSource.imgs.length + ' - ' +dbg + ' '+new Date().toISOString()); + } + duplicateCount++; + } + } else { + l.info('wikimedia.service.js addToImgList foFeaImg: unknown src "'+imgListWithSource.src+'" "'+imgNam+'" ' +dbg + ' '+new Date().toISOString()); + } + }; + if (0 < duplicateCount && debugAll + ) { + l.info('wikimedia.service.js addToImgList foFeaImg: '+duplicateCount+' duplicates found among ' +imgListWithSource.imgs.length + ' - ' +dbg + ' '+new Date().toISOString()); + } + if (1 < imgListWithSource.imgs.length && debugAll) { + if(process.env.NODE_ENV !== 'production') { + l.info('wikimedia.service.js addToImgList foFeaImg: added '+imgListWithSource.imgs.length+' ' +dbg); + } + } + } + return i; + } + + export function getImageInfo(img, dbg, showDetails){ let pageTitle = img.pgTit; return new Promise((resolve, reject) =>{ @@ -405,5 +365,63 @@ export function getImageInfo(img, dbg, showDetails){ } +export function getImgsOfCat(cat, dbg, city, imgUrlSet, imgUrls, dbgIdWd, fProps, debugAll) { + let catName = cat.c; + // if there is a gallery, then fetch all images in category + const imgsPerCat = 20; + let encCat = encodeURIComponent(catName); + let sanTit = sanitizeTitle(encCat); + let url = `https://commons.wikimedia.org/w/api.php?action=query&list=categorymembers&cmtype=file&cmlimit=${imgsPerCat}&cmtitle=Category:${sanTit}&prop=imageinfo&format=json`; + // make array of image promises + let imgValsCumul = []; + let imgNoInfoPomise = api.get(url, {timeout: 1000}) + .then(r => { + let category_members = r.data.query['categorymembers']; + let cI = 0; + cat.l = category_members.length; + if(process.env.NODE_ENV !== 'production' && debugAll) { + l.info('wikimedia.service.js getImgsOfCat: category "'+catName+'" has '+cat.l+' (limit '+imgsPerCat+') images '+dbg+' '+city+' '+ + dbgIdWd+' '+new Date().toISOString()); + } + // fetch information for each image, max 50 + for(; cI < cat.l && cI < 50;cI++) { + let page = category_members[cI]; + let dbgImg = "f-"+dbg+"_i-"+cI+"/"+cat.l; + let imgLikeFromWikiMedia = { + value: page.title.replace('File:',''), + typ:'wm' + } + let imgVals = []; + imgVals.push(imgLikeFromWikiMedia); + imgValsCumul.push(imgLikeFromWikiMedia); + let imgs = { src: 'wd', + imgs: imgVals, + type:'wm' }; + let addedC = addToImgList(imgs, imgUrlSet, imgUrls, dbg + ' '+ dbgIdWd+' cat "'+catName+'"', + debugAll,{n:catName,l:cat.l}); + }; + return Promise.all(imgValsCumul); + }).catch(err=> { + // If there is an error getting the category members, then reject with error + l.error('getImgsOfCat.categorymembers = api.get:\n'+ + `Failed to fetch category members. Cat "`+catName+'" ' +dbg + ' '+ dbgIdWd + + ' url '+url+'\n'+err.stack); + // add gallery as value of fountain gallery property + fProps.gallery.issues.push({ + data: err, + context: { + fountain_name: fProps.name.value, + property_id: 'gallery', + id_osm: fProps.id_osm.value, + id_wikidata: fProps.id_wikidata.value + }, + timeStamp: new Date(), + type: 'data_processing', + level: 'error', + message: `Failed to fetch category members from Wikimedia Commons. Url: ${url} `+dbg, + }); + }); + return imgNoInfoPomise; +} export default new WikimediaService(); \ No newline at end of file diff --git a/server/common/constants.js b/server/common/constants.js index c8f2ee78..fbbdcf0e 100644 --- a/server/common/constants.js +++ b/server/common/constants.js @@ -11,4 +11,5 @@ export const PROP_STATUS_WARNING = 'PROP_STATUS_WARNING'; export const PROP_STATUS_ERROR = 'PROP_STATUS_ERROR'; export const PROP_STATUS_FOUNTAIN_NOT_EXIST = 'PROP_STATUS_FOUNTAIN_NOT_EXIST'; export const PROP_STATUS_NOT_DEFINED = 'PROP_STATUS_NOT_DEFINED'; -export const PROP_STATUS_NOT_AVAILABLE = 'PROP_STATUS_NOT_AVAILABLE'; \ No newline at end of file +export const PROP_STATUS_NOT_AVAILABLE = 'PROP_STATUS_NOT_AVAILABLE'; +export const MAX_IMG_SHOWN_IN_GALLERY = 50; \ No newline at end of file