2017-07-24 11:40:03 +00:00
|
|
|
/*
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
Represents an album, which is made of many photos and videos
|
|
|
|
This is a virtual grouping of files, independent of the location on disk
|
|
|
|
A single photo/video could exist in multiple albums
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
const _ = require('lodash')
|
|
|
|
const path = require('path')
|
2019-01-06 20:48:57 +00:00
|
|
|
const slugify = require('slugify')
|
2021-02-23 22:52:26 +00:00
|
|
|
const url = require('./url')
|
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
var index = 0
|
2016-10-10 11:49:08 +00:00
|
|
|
|
|
|
|
// number of images to show in the album preview grid
|
2017-07-24 11:40:03 +00:00
|
|
|
const PREVIEW_COUNT = 10
|
2019-06-13 20:50:41 +00:00
|
|
|
const SLUGIFY_OPTIONS = { replacement: '-', remove: /[*+~.()'"!:@]/g }
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2017-07-24 11:40:03 +00:00
|
|
|
const SORT_ALBUMS_BY = {
|
2017-03-06 12:46:46 +00:00
|
|
|
'title': function (album) { return album.title },
|
|
|
|
'start-date': function (album) { return album.stats.fromDate },
|
|
|
|
'end-date': function (album) { return album.stats.toDate }
|
|
|
|
}
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2017-07-24 11:40:03 +00:00
|
|
|
const SORT_MEDIA_BY = {
|
|
|
|
'filename': function (file) { return file.filename },
|
|
|
|
'date': function (file) { return file.meta.date }
|
2017-03-06 12:46:46 +00:00
|
|
|
}
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2017-07-24 11:40:03 +00:00
|
|
|
const PREVIEW_MISSING = {
|
2016-10-16 11:24:07 +00:00
|
|
|
urls: {
|
2020-12-30 15:51:09 +00:00
|
|
|
thumbnail: 'public/missing.png',
|
|
|
|
small: 'public/missing.png'
|
2016-10-16 11:24:07 +00:00
|
|
|
}
|
2017-03-06 12:46:46 +00:00
|
|
|
}
|
2016-10-16 11:24:07 +00:00
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
function Album (opts) {
|
|
|
|
if (typeof opts === 'string') opts = { title: opts }
|
|
|
|
this.id = opts.id || ++index
|
|
|
|
this.title = opts.title || ('Album ' + this.id)
|
2019-06-13 20:50:41 +00:00
|
|
|
this.basename = slugify(this.title, SLUGIFY_OPTIONS)
|
2017-03-06 12:46:46 +00:00
|
|
|
this.files = opts.files || []
|
|
|
|
this.albums = opts.albums || []
|
|
|
|
this.depth = 0
|
|
|
|
this.home = false
|
|
|
|
this.stats = null
|
|
|
|
this.previews = null
|
2016-10-10 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
Album.prototype.finalize = function (options, parent) {
|
|
|
|
options = options || {}
|
|
|
|
var albumsOutputFolder = options.albumsOutputFolder || '.'
|
2017-02-07 12:19:22 +00:00
|
|
|
// calculate final file paths and URLs
|
|
|
|
if (parent == null) {
|
2017-03-08 09:12:05 +00:00
|
|
|
this.path = options.index || 'index.html'
|
|
|
|
this.url = options.index || 'index.html'
|
2017-03-06 12:46:46 +00:00
|
|
|
this.depth = 0
|
2017-02-07 12:19:22 +00:00
|
|
|
} else {
|
|
|
|
if (parent.depth > 0) {
|
2017-03-06 12:46:46 +00:00
|
|
|
this.basename = parent.basename + '-' + this.basename
|
2017-02-07 12:19:22 +00:00
|
|
|
}
|
2017-03-06 12:46:46 +00:00
|
|
|
this.path = path.join(albumsOutputFolder, this.basename + '.html')
|
2021-02-23 22:52:26 +00:00
|
|
|
this.url = url.fromPath(this.path)
|
2017-03-06 12:46:46 +00:00
|
|
|
this.depth = parent.depth + 1
|
2017-02-07 12:19:22 +00:00
|
|
|
}
|
2019-03-17 21:56:48 +00:00
|
|
|
// path to the optional ZIP file
|
|
|
|
if (options.albumZipFiles && this.files.length > 0) {
|
|
|
|
this.zip = this.path.replace(/\.[^\\/.]+$/, '.zip')
|
|
|
|
}
|
2017-02-07 12:19:22 +00:00
|
|
|
// then finalize all nested albums (which uses the parent basename)
|
2016-10-10 11:49:08 +00:00
|
|
|
for (var i = 0; i < this.albums.length; ++i) {
|
2017-03-06 12:46:46 +00:00
|
|
|
this.albums[i].finalize(options, this)
|
2016-10-10 11:49:08 +00:00
|
|
|
}
|
2016-10-16 11:24:07 +00:00
|
|
|
// perform stats & other calculations
|
|
|
|
// once the nested albums have been finalized too
|
2017-03-06 12:46:46 +00:00
|
|
|
this.home = this.depth === 0
|
|
|
|
this.calculateStats()
|
|
|
|
this.calculateSummary()
|
|
|
|
this.sort(options)
|
2020-12-30 15:51:09 +00:00
|
|
|
this.pickPreviews(options)
|
2017-03-06 12:46:46 +00:00
|
|
|
}
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
Album.prototype.calculateStats = function () {
|
2016-10-10 11:49:08 +00:00
|
|
|
// nested albums
|
2017-03-06 12:46:46 +00:00
|
|
|
var nestedPhotos = _.map(this.albums, 'stats.photos')
|
|
|
|
var nestedVideos = _.map(this.albums, 'stats.videos')
|
|
|
|
var nestedFromDates = _.map(this.albums, 'stats.fromDate')
|
|
|
|
var nestedToDates = _.map(this.albums, 'stats.toDate')
|
2016-10-10 11:49:08 +00:00
|
|
|
// current level
|
2018-12-11 22:11:03 +00:00
|
|
|
var currentPhotos = _.filter(this.files, { type: 'image' }).length
|
|
|
|
var currentVideos = _.filter(this.files, { type: 'video' }).length
|
2017-07-24 11:40:03 +00:00
|
|
|
var currentFromDate = _.map(this.files, 'meta.date')
|
|
|
|
var currentToDate = _.map(this.files, 'meta.date')
|
2016-10-10 11:49:08 +00:00
|
|
|
// aggregate all stats
|
|
|
|
this.stats = {
|
2017-03-06 12:46:46 +00:00
|
|
|
albums: this.albums.length,
|
|
|
|
photos: _.sum(_.compact(_.concat(nestedPhotos, currentPhotos))) || 0,
|
|
|
|
videos: _.sum(_.compact(_.concat(nestedVideos, currentVideos))) || 0,
|
2016-10-10 11:49:08 +00:00
|
|
|
fromDate: _.min(_.compact(_.concat(nestedFromDates, currentFromDate))),
|
2017-03-06 12:46:46 +00:00
|
|
|
toDate: _.max(_.compact(_.concat(nestedToDates, currentToDate)))
|
|
|
|
}
|
|
|
|
this.stats.total = this.stats.photos + this.stats.videos
|
2016-10-10 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
Album.prototype.calculateSummary = function () {
|
2016-10-10 11:49:08 +00:00
|
|
|
var items = [
|
|
|
|
itemCount(this.stats.albums, 'album'),
|
|
|
|
itemCount(this.stats.photos, 'photo'),
|
|
|
|
itemCount(this.stats.videos, 'video')
|
2017-03-06 12:46:46 +00:00
|
|
|
]
|
|
|
|
this.summary = _.compact(items).join(', ')
|
|
|
|
}
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
Album.prototype.sort = function (options) {
|
2020-05-10 22:36:46 +00:00
|
|
|
const sortAlbumsBy = getItemOrLast(options.sortAlbumsBy, this.depth)
|
|
|
|
const sortAlbumsDirection = getItemOrLast(options.sortAlbumsDirection, this.depth)
|
|
|
|
const sortMediaBy = getItemOrLast(options.sortMediaBy, this.depth)
|
|
|
|
const sortMediaDirection = getItemOrLast(options.sortMediaDirection, this.depth)
|
|
|
|
this.files = _.orderBy(this.files, SORT_MEDIA_BY[sortMediaBy], sortMediaDirection)
|
|
|
|
this.albums = _.orderBy(this.albums, SORT_ALBUMS_BY[sortAlbumsBy], sortAlbumsDirection)
|
2017-03-06 12:46:46 +00:00
|
|
|
}
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2020-12-30 15:51:09 +00:00
|
|
|
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
|
2017-03-06 12:46:46 +00:00
|
|
|
var missing = PREVIEW_COUNT - this.previews.length
|
2016-10-10 11:49:08 +00:00
|
|
|
for (var i = 0; i < missing; ++i) {
|
2017-03-06 12:46:46 +00:00
|
|
|
this.previews.push(PREVIEW_MISSING)
|
2016-10-10 11:49:08 +00:00
|
|
|
}
|
2017-03-06 12:46:46 +00:00
|
|
|
}
|
2016-10-10 11:49:08 +00:00
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
function itemCount (count, type) {
|
|
|
|
if (count === 0) return ''
|
|
|
|
var plural = (count > 1) ? 's' : ''
|
|
|
|
return '' + count + ' ' + type + plural
|
2016-10-10 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 22:36:46 +00:00
|
|
|
function getItemOrLast (array, index) {
|
|
|
|
if (typeof (array) === 'undefined') return undefined
|
|
|
|
if (typeof (array) === 'string') return array
|
|
|
|
if (index > array.length) return array[array.length - 1]
|
|
|
|
return array[index]
|
|
|
|
}
|
|
|
|
|
2016-11-02 00:35:27 +00:00
|
|
|
// for testing purposes
|
2017-03-06 12:46:46 +00:00
|
|
|
Album.resetIds = function () {
|
|
|
|
index = 0
|
|
|
|
}
|
2016-11-02 00:35:27 +00:00
|
|
|
|
2017-03-06 12:46:46 +00:00
|
|
|
module.exports = Album
|