diff --git a/controllers/photo.js b/controllers/photo.js index a852161b4..512c046bd 100755 --- a/controllers/photo.js +++ b/controllers/photo.js @@ -306,32 +306,61 @@ export async function find({ query, fieldSelect = {}, options = {}, populateUser } export function getNewPhotosLimit(user) { - let canCreate = 0; - const pfcount = user.pfcount; + return Math.max(0, getUserPhotosLimitMax(user) - user.pfcount); +} + +export function getNewPhotosLimitCss(user) { + const limit = getUserPhotosLimitMax(user); + return limit <= 10 ? 'photoLimit1' : limit <= 20 ? 'photoLimit2' : limit <= 75 ? 'photoLimit3' : ''; +} + +export function getUserPhotosLimitMax(user) { + // return user photos limit + /* needed fields: + user.rules.photoNewLimit + user.ranks + user.pcount + */ if (user.rules && _.isNumber(user.rules.photoNewLimit)) { - canCreate = Math.max(0, Math.min(user.rules.photoNewLimit, maxNewPhotosLimit) - pfcount); - } else if (user.ranks && (user.ranks.includes('mec_silv') || user.ranks.includes('mec_gold'))) { - // Silver and Gold metsenats have the maximum possible limit - canCreate = maxNewPhotosLimit - pfcount; - } else if (user.ranks && user.ranks.includes('mec')) { - // Metsenat has a limit of 150 - canCreate = Math.max(0, 150 - pfcount); - } else if (user.pcount < 15) { - canCreate = Math.max(0, 10 - pfcount); - } else if (user.pcount < 25) { - canCreate = Math.max(0, 15 - pfcount); - } else if (user.pcount < 50) { - canCreate = Math.max(0, 20 - pfcount); - } else if (user.pcount < 200) { - canCreate = Math.max(0, 50 - pfcount); - } else if (user.pcount < 1000) { - canCreate = Math.max(0, 75 - pfcount); - } else if (user.pcount >= 1000) { - canCreate = Math.max(0, 150 - pfcount); - } - - return canCreate; + return Math.min(user.rules.photoNewLimit, maxNewPhotosLimit); + } + + // Silver and Gold metsenats have the maximum possible limit + if (user.ranks && (user.ranks.includes('mec_silv') || user.ranks.includes('mec_gold'))) { + return maxNewPhotosLimit; + } + + // Metsenat has a limit of 150 + if (user.ranks && user.ranks.includes('mec')) { + return 150; + } + + if (user.pcount < 15) { + return 10; + } + + if (user.pcount < 25) { + return 15; + } + + if (user.pcount < 50) { + return 20; + } + + if (user.pcount < 200) { + return 50; + } + + if (user.pcount < 1000) { + return 75; + } + + if (user.pcount >= 1000) { + return 150; + } + + return 0; } /** @@ -1508,6 +1537,70 @@ async function givePhotos({ filter, options: { skip = 0, limit = 40, random = fa Photo.find(query, fieldsSelect, { lean: true, limit }).exec(), Photo.countDocuments(countQuery).exec(), ]); + } else if (iAm && iAm.registered && iAm.user.role >= 5 && + (!(filter.l === null) && filter.l || _.isEqual(filter.lf, [1]))) { + //aggregate вроде работает дольше по этому используем его только если необходимо + + let aggQuery = [ + { $lookup: { from: 'users', localField: 'user', foreignField: '_id', as: 'user_info' } }, + { $match: query }, + { $set: { photoNewLimit: { $sum: '$user_info.rules.photoNewLimit' } } }, + { $set: { pcount: { $sum: '$user_info.pcount' } } }, + { $set: { pfcount: { $sum: '$user_info.pfcount' } } }, + { $set: { ranks: { $first: '$user_info.ranks' } } }, + { $set: { photosLimit: { $sum: { $switch: { + branches: [ + { case: { $gt: ['$photoNewLimit', 0] }, then: { $min: ['$photoNewLimit', maxNewPhotosLimit] } }, + { case: { $in: [{ $max: '$ranks' }, ['mec_silv', 'mec_gold']] }, then: maxNewPhotosLimit }, + { case: { $in: [{ $max: '$ranks' }, ['mec']] }, then: 150 }, + { case: { $lt: ['$pcount', 15] }, then: 10 }, + { case: { $lt: ['$pcount', 25] }, then: 15 }, + { case: { $lt: ['$pcount', 50] }, then: 20 }, + { case: { $lt: ['$pcount', 200] }, then: 50 }, + { case: { $lt: ['$pcount', 1000] }, then: 75 }, + { case: { $gte: ['$pcount', 1000] }, then: 150 }, + ], + } } } } }, + ]; + + if (_.isEqual(filter.lf, [1])) { + aggQuery = [ + ...aggQuery, + { $match: { $expr: { $gte: ['$pfcount', '$photosLimit'] } } }, + ]; + } + + if (filter.l) { + aggQuery = [ + ...aggQuery, + { $match: { photosLimit: { $lt: Number(filter.l.max) } } }, + ]; + } + + [photos, count] = await Promise.all([ + Photo.aggregate([ + ... aggQuery, + { $sort: { sdate: -1 } }, + { $skip: skip }, + { $limit: limit }, + { $project: { 'photosLimit': 1, 'pcount': 1, 'pfcount': 1, 'photoNewLimit': 1, 'ranks': 1, 'user_info.cid': 1, 'user_info.pfcount': 1, 'user_info.pcount': 1, 'user_info.ranks': 1, 'user_info.rules': 1, ...fieldsSelect } }, + ]), + Photo.aggregate([ + ...aggQuery, + { $count: 'photos_count' }, + ]), + ]); + + // take count value + count = count.length ? count[0].photos_count : 0; + } else if (iAm && iAm.registered && iAm.user.role >= 5) { + //to show different colors make sence only for moderators + [photos, count] = await Promise.all([ + Photo.find(query, fieldsSelect, { lean: true, skip, limit, sort: { sdate: -1 } }) + .populate({ path: 'user', select: { pfcount: 1, pcount: 1, ranks: 1, rules: 1 } } + ).exec(), + Photo.countDocuments(query).exec(), + ]); } else { [photos, count] = await Promise.all([ Photo.find(query, fieldsSelect, { lean: true, skip, limit, sort: { sdate: -1 } }).exec(), @@ -1527,6 +1620,13 @@ async function givePhotos({ filter, options: { skip = 0, limit = 40, random = fa await this.call('photo.fillPhotosProtection', { photos, theyAreMine: itsMineGallery, setMyFlag: !userId }); for (const photo of photos) { + if (iAm.user.role >= 5 && photo.s <= 2) { + //set user limit for moderator view + photo.userPhotoLimitCss = getNewPhotosLimitCss(photo.user_info ? photo.user_info[0] : photo.user); + } else { + photo.userPhotoLimitCss = ''; + } + photo._id = undefined; photo.user = undefined; photo.vdate = undefined; @@ -1588,6 +1688,8 @@ async function givePhotos({ filter, options: { skip = 0, limit = 40, random = fa s: buildQueryResult.s, y: buildQueryResult.y, c: buildQueryResult.c, + l: buildQueryResult.l, + lf: buildQueryResult.lf, geo: filter.geo, }, }; @@ -1615,7 +1717,7 @@ const givePublicNoGeoIndex = (function () { }; }()); -const filterProps = { geo: [], r: [], rp: [], rs: [], re: [], s: [], t: [], y: [], c: [] }; +const filterProps = { geo: [], r: [], rp: [], rs: [], re: [], s: [], t: [], y: [], c: [], l: [], lf: [] }; const delimeterParam = '_'; const delimeterVal = '!'; export function parseFilter(filterString) { @@ -1790,6 +1892,53 @@ export function parseFilter(filterString) { } } } + } else if (filterParam === 'l') { + filterVal = filterVal.split(delimeterVal); + + if (Array.isArray(filterVal) && filterVal.length === 1) { + filterVal = filterVal.map(Number).sort(); + + // set l.max (max user limit) only if value l is choosen in link and between 1 and 9999 + if (!_.isEqual(filterVal, [0])) { + const [l0] = filterVal; + const l = {}; + let active = true; + + if (l0 > 0 && l0 < 1e5) { + l.max = l0; + } else { + active = false; + } + + if (active) { + result.l = l; + } + } else { + const l = {}; + + result.l = l; + } + } + } else if (filterParam === 'lf') { + filterVal = filterVal.split(delimeterVal); + + if (Array.isArray(filterVal) && filterVal.length) { + result.lf = []; + + for (filterValItem of filterVal) { + if (filterValItem) { + filterValItem = Number(filterValItem); + + if (typesSet.has(filterValItem)) { + result.lf.push(filterValItem); + } + } + } + + if (!result.lf.length) { + delete result.lf; + } + } } } } @@ -3163,6 +3312,16 @@ export function buildPhotosQuery(filter, forUserId, iAm, random) { result.c = filter.c; } + if (filter.l) { + // добавляется напрямую в агрегативной функции + result.l = filter.l; + } + + if (filter.lf) { + // добавляется напрямую в агрегативной функции + result.lf = filter.lf; + } + if (random) { if (!query) { query = {}; diff --git a/public/js/module/photo/gallery.js b/public/js/module/photo/gallery.js index 960f7edc6..28e778b4d 100644 --- a/public/js/module/photo/gallery.js +++ b/public/js/module/photo/gallery.js @@ -41,6 +41,7 @@ define([ this.t = []; this.ccount = 1; + this.lcount = 75; this.year = statuses.years[statuses.type.PAINTING].min; this.year2 = statuses.years[statuses.type.PHOTO].max; @@ -69,6 +70,9 @@ define([ s: ko.observableArray(), c: ko.observableArray(), ccount: ko.observable(this.ccount), + l: ko.observableArray(), + lcount: ko.observable(this.lcount), + lf: ko.observableArray(), // limit for user is full r: ko.observableArray(), // Array of selected regions rdis: ko.observableArray(), // Array of cids of inactive regions rs: ko.observableArray(), // Enable/disable subregions @@ -243,6 +247,7 @@ define([ this.subscriptions.filter_disp_y = this.filter.disp.year.subscribe(_.debounce(this.yearHandle, 800), this); this.subscriptions.filter_disp_y2 = this.filter.disp.year2.subscribe(_.debounce(this.year2Handle, 800), this); this.subscriptions.filter_disp_ccount = this.filter.disp.ccount.subscribe(_.debounce(this.ccountHandle, 800), this); + this.subscriptions.filter_disp_lcount = this.filter.disp.lcount.subscribe(_.debounce(this.lcountHandle, 800), this); this.subscriptions.filter_active = this.filter.active.subscribe(this.filterActiveChange, this); this.filterChangeHandleBlock = false; @@ -392,6 +397,8 @@ define([ let filterString = ''; const t = this.filter.disp.t().map(Number).sort(); const c = this.filter.disp.c().map(Number).sort(); + const l = this.filter.disp.l().map(Number).sort(); + const lf = this.filter.disp.lf().map(Number).sort(); const r = this.filter.disp.r(); const re = this.filter.disp.re(); let s = this.filter.disp.s().map(Number); @@ -399,6 +406,7 @@ define([ const year = Number(this.filter.disp.year()); const year2 = Number(this.filter.disp.year2()); const ccount = Number(this.filter.disp.ccount()); + const lcount = Number(this.filter.disp.lcount()); const yearsRange = this.getTypeYearsRange(); let i; @@ -501,6 +509,24 @@ define([ } } + if (l.length && (_.isEqual(l, [0]) || lcount > 0)) { + this.lcount = lcount; + + filterString += (filterString ? '_' : '') + 'l'; + + if (_.includes(l, 0)) { + filterString += '!0'; + } + + if (_.includes(l, 1)) { + filterString += '!' + (lcount > 1 ? lcount : 1); + } + } + + if (lf.length && _.isEqual(lf, [1])) { + filterString += (filterString ? '_' : '') + 'lf!1'; + } + return filterString; }, filterActiveChange: function (val) { @@ -1054,6 +1080,72 @@ define([ return true; } }, + flclick: function (data, event) { + const currL = data.filter.disp.l(); + const clicked = event.target.value; + + if (currL.length === 2) { + data.filter.disp.l([clicked]); + } + + this.filterChangeHandle(); //Вручную вызываем обработку фильтра + + return true; //Возвращаем true, чтобы галка в браузере переключилась + }, + lcountHandle: function (lcount) { + lcount = Number(lcount); + + if (!lcount || lcount < 1) { + this.filter.disp.lcount('1'); + + return; + } + + if (lcount > 9999) { + this.filter.disp.lcount('9999'); + + return; + } + + if (_.includes(this.filter.disp.l(), '1') && lcount !== this.lcount) { + // Вручную вызываем обработку фильтра + this.filterChangeHandle(); + } + }, + lcountArrow: function (data, evt) { + let lcount = Number(this.filter.disp.lcount()); + + switch (evt.key) { + case 'ArrowUp': + if (lcount < 9999) { + lcount = lcount + 1; + this.filter.disp.lcount(String(lcount)); + } + + break; + case 'ArrowDown': + if (lcount > 1) { + this.filter.disp.lcount(String(lcount - 1)); + } + + break; + default: + return true; + } + }, + flfclick: function (data, event) { + const checkedType = event.target.checked; + + if (checkedType) { + data.filter.disp.lf(['1']); + } else { + data.filter.disp.lf(['0']); + } + + this.filterChangeHandle(); //Вручную вызываем обработку фильтра + + return true; //Возвращаем true, чтобы галка в браузере переключилась + }, updateFilterUrl: function (filterString) { const uri = new Uri(location.pathname + location.search); @@ -1300,6 +1392,27 @@ define([ this.filter.disp.c(c.map(String)); + let l; + + if (!_.isEmpty(data.filter.l)) { + l = []; + + if (data.filter.l.max > 0) { + l.push(1); + this.lcount = data.filter.l.max; + this.filter.disp.lcount(String(this.lcount)); + } + + this.filter.disp.l(l.map(String)); + } + + if (!data.filter.lf || !data.filter.lf.length) { + data.filter.lf = [0]; + } + + this.lf = data.filter.lf.map(String); + this.filter.disp.lf(this.lf.slice()); + this.filterChangeHandleBlock = false; } diff --git a/public/style/photo/gallery.less b/public/style/photo/gallery.less index 163616bb3..103b3e4a8 100644 --- a/public/style/photo/gallery.less +++ b/public/style/photo/gallery.less @@ -40,7 +40,8 @@ } .fVars .yearsSet > .years, - .fVars .ccountSet { + .fVars .ccountSet, + .fVars .lcountSet { font-weight: normal; color: @gray-light; } @@ -158,7 +159,8 @@ } &.yearsSet > .years, - .ccountSet { + .ccountSet, + .lcountSet { font-weight: bold; color: @MainBlueColor; } @@ -366,6 +368,18 @@ text-align: left; } + .photoLimit1 { + background-color: #7c0000; + } + + .photoLimit2 { + background-color: #593000; + } + + .photoLimit3 { + background-color: #004300; + } + .photoPreview { &.addPhoto { color: #b9b9b9; diff --git a/views/module/photo/gallery.pug b/views/module/photo/gallery.pug index c8a7951db..5851a2100 100644 --- a/views/module/photo/gallery.pug +++ b/views/module/photo/gallery.pug @@ -165,6 +165,20 @@ | Войдите, чтобы видеть больше // /ko + //ko if: auth.iAm && auth.iAm.role() >= 5 + .fRow + .fName Лимит пользователя: + .fVars + label.checkbox-inline + input(type="checkbox", value="1", data-bind="checked: filter.disp.lf, click: flfclick") + span Исчерпан + div(style="display:inline-block;") + label.checkbox-inline(style="margin-right:0;min-width:auto;") + input(type="checkbox", value="1", data-bind="checked: filter.disp.l, click: flclick") + = 'Меньше' + = ' ' + input.form-control.ccount(type="text", data-bind="value: filter.disp.lcount, valueUpdate: 'keyup', event: {keydown: lcountArrow}, attr: {disabled:filter.disp.l.indexOf('1')<0}, css: {lcountSet: filter.disp.lcount()>1 && filter.disp.l.indexOf('1')>=0}", maxlength="4") + // /ko .row.rowoffset .col-xs-12.col-sm-7.col-lg-4(data-bind="css: {invisible: !loadedFirst()}") .col-xs-12.col-sm-5.col-lg-4.col-lg-push-4.switch(data-bind="css: {invisible: !loadedFirst()}") @@ -199,7 +213,7 @@ .desc Добавить.. // /ko //ko foreach: photos - .photoPreview.withStatus.withInfo.fringe(data-bind="attr: {title: title}, css: ($parent.feed() ? 'trans ' : '') + 's' + $data.s") + .photoPreview.withStatus.withInfo.fringe(data-bind="attr: {title: title}, css: ($parent.feed() ? 'trans ' : '') + 's' + $data.s + ' ' + $data.userPhotoLimitCss") a.photoBox(data-bind="attr: {href: '/p/' + cid, target: $parent.feed() ? '_blank' : '_self'}") img.img(data-bind="attr: {src: sfile, alt: title}, event: {load: $parent.onPreviewLoad, error: $parent.onPreviewErr}, style: {width: $parent.w, height: $parent.h}") .curtain