feat(core): option to choose how album previews are selected

Fixes #223
pull/224/head
Romain 3 years ago
parent ba186d5493
commit b7827941f4

@ -238,6 +238,12 @@ const OPTIONS = {
type: 'boolean',
'default': false
},
'album-previews': {
group: 'Album options:',
description: 'How previews are selected',
choices: ['first', 'spread', 'random'],
'default': 'first'
},
// ------------------------------------
// Website options

@ -29,7 +29,8 @@ const SORT_MEDIA_BY = {
const PREVIEW_MISSING = {
urls: {
thumbnail: 'public/missing.png'
thumbnail: 'public/missing.png',
small: 'public/missing.png'
}
}
@ -76,7 +77,7 @@ Album.prototype.finalize = function (options, parent) {
this.calculateStats()
this.calculateSummary()
this.sort(options)
this.pickPreviews()
this.pickPreviews(options)
}
Album.prototype.calculateStats = function () {
@ -119,15 +120,31 @@ Album.prototype.sort = function (options) {
this.albums = _.orderBy(this.albums, SORT_ALBUMS_BY[sortAlbumsBy], sortAlbumsDirection)
}
Album.prototype.pickPreviews = function () {
// also consider previews from nested albums
var nestedPicks = _.flatten(_.map(this.albums, 'previews')).filter(function (file) {
return file !== PREVIEW_MISSING
})
// then pick the top ones
var potentialPicks = _.concat(this.files, nestedPicks)
this.previews = potentialPicks.slice(0, PREVIEW_COUNT)
// and fill the gap with a placeholder
Album.prototype.pickPreviews = function (options) {
// consider nested albums if there aren't enough photos
var potential = this.files
if (potential.length < PREVIEW_COUNT) {
const nested = _.flatMap(this.albums, 'previews').filter(file => file !== PREVIEW_MISSING)
potential = potential.concat(nested)
}
// choose the previews
if (!options.albumPreviews || options.albumPreviews === 'first') {
this.previews = _.slice(potential, 0, PREVIEW_COUNT)
} else if (options.albumPreviews === 'random') {
this.previews = _.sampleSize(potential, PREVIEW_COUNT)
} else if (options.albumPreviews === 'spread') {
if (potential.length < PREVIEW_COUNT) {
this.previews = _.slice(potential, 0, PREVIEW_COUNT)
} else {
const bucketSize = Math.floor(potential.length / PREVIEW_COUNT)
const buckets = _.chunk(potential, bucketSize)
this.previews = buckets.slice(0, PREVIEW_COUNT).map(b => b[0])
}
} else {
throw new Error(`Unsupported preview type: ${options.albumPreviews}`)
}
// and fill any gap with a placeholder
var missing = PREVIEW_COUNT - this.previews.length
for (var i = 0; i < missing; ++i) {
this.previews.push(PREVIEW_MISSING)

@ -40,7 +40,11 @@ exports.date = function (str) {
}
exports.photo = function (opts) {
opts = opts || {}
if (typeof opts === 'string') {
opts = { path: opts }
} else {
opts = opts || {}
}
opts.mimeType = 'image/jpg'
return exports.file(opts)
}

@ -0,0 +1,112 @@
const _ = require('lodash')
const path = require('path')
const should = require('should/as-function')
const Album = require('../../src/model/album')
const fixtures = require('../fixtures')
function arrayOfFiles (count) {
const base = new Array(count)
return Array.from(base, (_, index) => fixtures.photo(`${index}`))
}
function outputName (output) {
const ext = path.extname(output.urls.thumbnail)
return path.basename(output.urls.thumbnail, ext)
}
describe('Album', function () {
describe('previews', function () {
it('picks the first 10 photos by default', function () {
const album = new Album({ files: arrayOfFiles(100) })
album.finalize()
should(album.previews).have.length(10)
const thumbs = album.previews.map(outputName)
should(thumbs).eql(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
})
it('adds <missing> thumbnails to fill', function () {
const album = new Album({ files: arrayOfFiles(5) })
album.finalize()
const thumbs = album.previews.map(outputName)
should(thumbs.slice(0, 5)).eql(['0', '1', '2', '3', '4'])
for (var i = 5; i < 10; ++i) {
should(album.previews[i].urls.thumbnail).eql('public/missing.png')
}
})
it('uses files from nested albums too', function () {
const album = new Album({
title: 'a',
files: [fixtures.photo('a1'), fixtures.photo('a2')],
albums: [
new Album({
title: 'b',
files: [fixtures.photo('b1'), fixtures.photo('b2')]
}),
new Album({
title: 'c',
files: [fixtures.photo('c1'), fixtures.photo('c2')]
})
]
})
album.finalize()
should(album.previews).have.length(10)
const thumbs = album.previews.map(outputName)
should(thumbs.slice(0, 6)).eql(['a1', 'a2', 'b1', 'b2', 'c1', 'c2'])
for (var i = 6; i < 10; ++i) {
should(album.previews[i].urls.thumbnail).eql('public/missing.png')
}
})
describe('preview modes', () => {
it('can pick the first 10 photos', function () {
const album = new Album({ files: arrayOfFiles(100) })
album.finalize({ albumPreviews: 'first' })
should(album.previews).have.length(10)
const thumbs = album.previews.map(outputName)
should(thumbs).eql(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
})
it('can randomize the previews', function () {
const album = new Album({ files: arrayOfFiles(100) })
album.finalize({ albumPreviews: 'random' })
should(album.previews).have.length(10)
should(_.uniq(album.previews)).have.length(10)
})
it('can spread the previews', function () {
const album = new Album({ files: arrayOfFiles(50) })
album.finalize({ albumPreviews: 'spread' })
should(album.previews).have.length(10)
const thumbs = album.previews.map(outputName)
should(thumbs).eql(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45'])
})
it('ignores the extra photos when spreading on un-even counts', function () {
const album = new Album({ files: arrayOfFiles(58) })
album.finalize({ albumPreviews: 'spread' })
should(album.previews).have.length(10)
const thumbs = album.previews.map(outputName)
should(thumbs).eql(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45'])
})
it('picks the first 10 when trying to spread under 10 photos', function () {
const album = new Album({ files: arrayOfFiles(5) })
album.finalize({ albumPreviews: 'spread' })
should(album.previews).have.length(10)
const thumbs = album.previews.map(outputName)
should(thumbs.slice(0, 5)).eql(['0', '1', '2', '3', '4'])
for (var i = 5; i < 10; ++i) {
should(album.previews[i].urls.thumbnail).eql('public/missing.png')
}
})
it('throws an error if the preview type is not supported', function () {
const album = new Album({ files: arrayOfFiles(5) })
should.throws(function () {
album.finalize({ albumPreviews: 'test' })
})
})
})
})
})

@ -111,48 +111,6 @@ describe('Album', function () {
})
})
describe('previews', function () {
it('picks the first 10 files as previews', function () {
const a = new Album({ files: [
fixtures.photo(), fixtures.photo(), fixtures.photo(), fixtures.photo(),
fixtures.photo(), fixtures.photo(), fixtures.photo(), fixtures.photo(),
fixtures.photo(), fixtures.photo(), fixtures.photo(), fixtures.photo()
] })
a.finalize()
should(a.previews).have.length(10)
})
it('adds <missing> thumbnails to fill', function () {
const a = new Album({ files: [
fixtures.photo(), fixtures.photo()
] })
a.finalize()
should(a.previews[2].urls.thumbnail).eql('public/missing.png')
should(a.previews[9].urls.thumbnail).eql('public/missing.png')
})
it('uses files from nested albums too', function () {
const a = new Album({
title: 'a',
albums: [
new Album({
title: 'b',
files: [fixtures.photo(), fixtures.photo()]
}),
new Album({
title: 'c',
files: [fixtures.photo(), fixtures.photo()]
})
]
})
a.finalize()
should(a.previews).have.length(10)
for (var i = 0; i < 4; ++i) {
should(a.previews[i].urls.thumbnail).not.eql('public/missing.png')
}
})
})
describe('sorting', function () {
it('can sort albums by title', function () {
const a = new Album('A')

Loading…
Cancel
Save