intranet-pictures 2.1.0 → 3.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 389f9f8c45887f26425f96b166204dda1c9edcc443b8e6b70fc83dc3ce8e6ab4
4
- data.tar.gz: 8cd7e0f11ae898d2ccee675f87d14facacf84b472c37d456f2a5ba2b8ed68d64
3
+ metadata.gz: 42e09da26c3eecce0bdf0c37f9f39ae4db140b850c594677f87672d64ae90a23
4
+ data.tar.gz: fd202aa0c01b91752021116f3bf455497e153a32ce49a4d4b9ce59649b5916fa
5
5
  SHA512:
6
- metadata.gz: a26dab3d6b2d07d29bde79c2ce8b6fe8c95a3d3f0e7bf5fd90bf9537619b9ef5cae2d947c2ba6b068919c0048bbfc94612d02abb534510626e9d04ab5e97b0d3
7
- data.tar.gz: 71ef4e5113ee75f4067c22dd0623bb17fbf651cbbb5f91aa9440c0ec19391f4f21faab2aa5cac3e8672084cef6448287e6d5fc5cb021eb01863c86287ed31472
6
+ metadata.gz: 80ce234ae592e9073e61b2287a8cc83a228afb8afe2978072efeac125aedeeba31d201d96d714e5eadb54c2e877c34424f86e67a1599aadc87f54ce60f8a690e
7
+ data.tar.gz: 359976b4df45fb420eee0ebc7d03bd191886c98947815faae54114c3bbcac773b58c45c663ca16e5c6a221c0903cf15ee3963f789b50572c0ef8a4c3d6c1910b
@@ -10,26 +10,26 @@ module Intranet
10
10
  # === Structure of the JSON database
11
11
  # See the example below.
12
12
  #
13
- # The title of the pictures gallery is indicated in +title+
14
- #
15
13
  # Pictures are described individually as a hash in the +pictures+ array. The mandatory keys in
16
14
  # this hash are:
17
- # * +uri+ : location of the picture, relative to the JSON file
18
- # * +title+ : description of the picture
19
- # * +datetime+ : date and time of the picture, as a string in ISO8601 format
20
- # * +height+ and +width+ : size in pixels of the picture
15
+ # * +uri+ : location of the picture, relative to the JSON file (string)
16
+ # * +title+ : description of the picture (string)
17
+ # * +datetime+ : date and time of the picture, in ISO8601 format (string)
18
+ # * +height+ and +width+ : size in pixels of the picture (integers)
21
19
  #
22
20
  # Additional keys may be added (for instance: event, city, region/country, ...); they will be
23
- # used to group pictures sharing the same value of a given key.
21
+ # used to select pictures sharing the same value of a given key, or to group them.
22
+ # Values may either be singular string elements (for instance: "My wonderful event") or array
23
+ # of strings (for instance: [ "Casino", "Las Vegas", "Urban photography" ]).
24
+ #
24
25
  # Some groups may be defined in the +groups+ hash to add a thumbnail picture and a brief text
25
26
  # description for them. The possible keys in this hash are:
26
- # * +id+ : unique identifier of the group, mandatory
27
- # * +brief+ : optional short text associated to the group name
28
- # * +uri+ : optional group thumbnail, relative to the JSON file
27
+ # * +id+ : unique identifier of the group, mandatory (string)
28
+ # * +brief+ : optional short text associated to the group name (string)
29
+ # * +uri+ : optional group thumbnail, relative to the JSON file (string)
29
30
  #
30
- # @example Structure of the JSON database (not all mandatory are present for readability)
31
+ # @example Structure of the JSON database (not all mandatory keys are present for readability)
31
32
  # {
32
- # "title": "gallery title",
33
33
  # "groups": {
34
34
  # "event": [
35
35
  # { "id": "Party", "brief": "...", "uri": "party.jpg", ... },
@@ -41,7 +41,8 @@ module Intranet
41
41
  # ]
42
42
  # },
43
43
  # "pictures": [
44
- # { "uri": "dir/pic0.jpg", "datetime": "...", "event": "Party", "city": "Houston", ... },
44
+ # { "uri": "dir/pic0.jpg", "datetime": "...", "event": "Party", "city": "Houston",
45
+ # "tags": [ "USA", "Portrait" ], ... },
45
46
  # { ... }
46
47
  # ]
47
48
  # }
@@ -55,17 +56,16 @@ module Intranet
55
56
  load_json
56
57
  end
57
58
 
58
- # Returns the pictures gallery title.
59
- # @return [String] The gallery title.
60
- # @raise KeyError If no title is defined in JSON file.
61
- def title
62
- load_json
63
- @json.fetch('title').to_s
64
- end
65
-
66
- # Returns the list of the pictures matching +selector+.
67
- # Results are returned ordered by +sort_by+ in ascending order if +asc+, and in descending
68
- # order otherwise.
59
+ # Returns the list of the pictures matching the given selectors.
60
+ # Selectors are key/value strings pairs restricting the pictures to be returned:
61
+ # - if a picture has the given key, its value is a singular string and is strictly equal
62
+ # to the given value, it is returned;
63
+ # - if a picture has the given key, its value is an array of strings and ONE of the
64
+ # elements of this array is equal to the given value, it is returned;
65
+ # - all other pictures are not returned.
66
+ # Results are sorted according to the value of the key +sort_by+, in ascending order if
67
+ # +asc+, and in descending order otherwise. Sorting by a key whose value is not a singular
68
+ # string is not defined.
69
69
  # @param selector [Hash<String,String>] The pictures selector, interpreted as a logical AND
70
70
  # combination of all key/value pairs provided.
71
71
  # @param sort_by [String] The picture field to sort the results by, or nil if results should
@@ -74,11 +74,16 @@ module Intranet
74
74
  # in descending order.
75
75
  # @return [Array<Hash{'title'=>String, 'datetime'=>String, 'height'=>Integer,
76
76
  # 'width'=>Integer, ...}>] The selected pictures.
77
- # @raise KeyError If JSON file is malformed, or if +sort_by+ is not an existing picture field.
77
+ # @raise KeyError If JSON file is malformed, or if +sort_by+ is not an existing picture field,
78
+ # or if +sort_by+ is not a singular string value.
78
79
  def list_pictures(selector = {}, sort_by = nil, asc = true)
79
80
  load_json
80
81
  pics = select_pictures(selector).map { |p| p.except('uri') }
81
- pics.sort_by! { |p| p.fetch(sort_by) } unless sort_by.nil?
82
+ unless sort_by.nil?
83
+ raise KeyError if pics.any? { |p| p.fetch(sort_by).respond_to?('any?') }
84
+
85
+ pics.sort_by! { |p| p.fetch(sort_by) }
86
+ end
82
87
  pics.reverse! unless asc
83
88
  pics
84
89
  end
@@ -151,7 +156,14 @@ module Intranet
151
156
  end
152
157
 
153
158
  def picture_match?(picture, selector)
154
- selector.all? { |k, v| picture.fetch(k) == v }
159
+ selector.all? do |k, v|
160
+ picture_value = picture.fetch(k)
161
+ if picture_value.respond_to?('any?')
162
+ picture_value.any? { |e| e == v }
163
+ else
164
+ picture_value == v
165
+ end
166
+ end
155
167
  rescue KeyError
156
168
  false
157
169
  end
@@ -9,7 +9,7 @@ require_relative 'version'
9
9
  module Intranet
10
10
  module Pictures
11
11
  # The responder for the Pictures monitor module of the Intranet.
12
- class Responder < AbstractResponder # rubocop:disable Metrics/ClassLength
12
+ class Responder < AbstractResponder
13
13
  include Core::HamlWrapper # 'inherits' from methods of HamlWrapper
14
14
 
15
15
  # Returns the name of the module.
@@ -35,27 +35,15 @@ module Intranet
35
35
  # The pictures provider.
36
36
  # @see Intranet::Pictures::JsonDbProvider The specification of the provider, and in particular
37
37
  # the minimal mandatory elements that must be returned by the operations.
38
- # @param recents [Array<Hash{group_by:String, sort_by:String, sort_order:String,
39
- # limit:Integer}>]
40
- # The description of the recent pictures to be displayed on the module home page. Pictures
41
- # will first be sorted according to +sort_by+ and +sort_order+, then grouped by +group_by+,
42
- # keeping only the first +limit+ elements, or all if +limit+ is zero. All keys except
43
- # +group_by+ may be omitted.
44
- # @param home_groups [Array<Hash{group_by:String, sort_by:String, sort_order:String,
45
- # browse_group_by:String, browse_sort_by:String, browse_sort_order:String}>]
46
- # The description of the pictures groups to be displayed on the module home page, after the
47
- # +recents+. Pictures will first be sorted according to +sort_by+ and +sort_order+, then
48
- # grouped by +group_by+. The obtained groups will be displayed with their thumbnail,
49
- # (optional) brief text and a link to display images of that group sorted according to
50
- # +browse_sort_by+ and +browse_sort_order+ and grouped by +browse_group_by+. All keys except
51
- # +group_by+ and +browse_group_by+ may be omitted.
38
+ # @param defaults [Hash{group_by:String, sort_by:String, sort_order:String}]
39
+ # The default grouping and sort order, if none is specified in the URL.
40
+ # All keys except +group_by+ may be omitted.
52
41
  # @param in_menu [Boolean] Whether the module instance should be displayed in the main
53
42
  # navigation menu or not.
54
- def initialize(provider, recents = [], home_groups = [], in_menu = true)
43
+ def initialize(provider, defaults, in_menu = true)
55
44
  @provider = provider
56
- @recents = recents
45
+ @defaults = defaults
57
46
  @in_menu = in_menu
58
- @home_groups = home_groups
59
47
  end
60
48
 
61
49
  # Specifies if the responder instance should be displayed in the main navigation menu or not.
@@ -99,18 +87,17 @@ module Intranet
99
87
  # ]
100
88
  def generate_page(path, query)
101
89
  case path
102
- when %r{^/index\.html$} then serve_home
103
- when %r{^/browse\.html$} then serve_browse(query)
90
+ when %r{^/index\.html$} then serve_home(query)
104
91
  when %r{^/api/} then serve_api(path.gsub(%r{^/api}, ''), query)
105
92
  when %r{^/i18n\.js$} then serve_i18n_js
106
- else super(path, query)
93
+ else super
107
94
  end
108
95
  end
109
96
 
110
- # Returns the title of the Pictures module, as displayed on the web page.
111
- # @return [String] The title of the Pictures module web page.
97
+ # The title of the Pictures module, as displayed on the web page.
98
+ # @return [String] The title of the Pictures web page.
112
99
  def title
113
- @provider.title
100
+ I18n.t('pictures.menu')
114
101
  end
115
102
 
116
103
  private
@@ -126,24 +113,31 @@ module Intranet
126
113
  # @return [String] The key to use to group pictures.
127
114
  # @raise KeyError If no grouping criteria is specified.
128
115
  def group_by(query)
129
- query.fetch('group_by')
116
+ return query['group_by'] unless query['group_by'].nil?
117
+
118
+ @defaults.fetch('group_by')
130
119
  end
131
120
 
132
121
  # Extract the sorting criteria from the given +query+, ie. the key that will be used to sort
133
122
  # the selected pictures.
134
123
  # @return [String] The key to use to sort pictures, or nil if no sorting is specified.
135
124
  def sort_by(query)
136
- query.fetch('sort_by')
137
- rescue KeyError
138
- nil
125
+ return query['sort_by'] unless query['sort_by'].nil?
126
+
127
+ @defaults['sort_by']
139
128
  end
140
129
 
141
130
  # Extract the sorting order from the given +query+.
142
131
  # @return [Boolean] True if the pictures should be sorted in ascending order, False otherwise.
143
132
  # @raise KeyError If the query requests an invalid sort order.
144
133
  def sort_order(query)
145
- return false if query['sort_order'] == 'desc'
146
- return true if query['sort_order'].nil? || query['sort_order'] == 'asc'
134
+ order = query['sort_order']
135
+ order = @defaults['sort_order'] if order.nil?
136
+ if order == 'desc'
137
+ return false
138
+ elsif order.nil? || order == 'asc'
139
+ return true
140
+ end
147
141
 
148
142
  raise KeyError # incorrect value for 'sort_order'
149
143
  end
@@ -166,68 +160,43 @@ module Intranet
166
160
  ##########################################################################
167
161
 
168
162
  def collect_groups(selector, group_by, sort_by, sort_order)
169
- # Select all pictures, sort them & eventually keep only group names (first occurrence only)
163
+ # Select all pictures & sort them
164
+ pics = @provider.list_pictures(selector, sort_by, sort_order)
165
+ return [] if pics.empty?
166
+
167
+ # Eventually keep only group names (first occurrence only)
170
168
  @provider.list_pictures(selector, sort_by, sort_order).map do |picture|
171
- picture.fetch(group_by)
172
- end.uniq
169
+ picture[group_by] # may be empty
170
+ end.uniq.compact
173
171
  end
174
172
 
175
173
  def hash_to_query(hash)
176
174
  hash.map { |k, v| [k, v].join('=') }.join('&')
177
175
  end
178
176
 
179
- def recent_groups
180
- @recents.map do |recent|
181
- groups = collect_groups({}, group_by(recent), sort_by(recent), sort_order(recent))
182
- see_more_url = ''
183
- if recent['limit'].to_i.positive?
184
- groups = groups.first(recent['limit'].to_i)
185
- see_more_url = "browse.html?#{hash_to_query(recent.except('limit'))}"
186
- end
187
- { group_key: group_by(recent), groups: groups, see_more_url: see_more_url }
188
- end
189
- end
190
-
191
- def all_groups
192
- @home_groups.map do |sec|
193
- groups = collect_groups({}, group_by(sec), sort_by(sec), sort_order(sec))
194
- url_prefix = "browse.html?group_by=#{sec['browse_group_by']}"
195
- url_prefix += "&sort_by=#{sec['browse_sort_by']}" if sec['browse_sort_by']
196
- url_prefix += "&sort_order=#{sec['browse_sort_order']}" if sec['browse_sort_order']
197
- { group_key: group_by(sec), groups: groups, url_prefix: url_prefix }
198
- end
177
+ def all_tags(selector)
178
+ @provider.list_pictures(selector).map do |picture|
179
+ picture['tags'] # may be empty
180
+ end.flatten.compact.sort.uniq
199
181
  end
200
182
 
201
- def make_nav(query = {})
202
- h = { I18n.t('nav.home') => '/index.html', I18n.t('pictures.menu') => nil, title => nil }
203
- unless query['group_by'].nil?
204
- h[title] = 'index.html'
205
- extra_key = I18n.t("pictures.nav.#{group_by(query)}")
206
- filters = selector(query).values
207
- extra_key += " (#{filters.join(', ')})" unless filters.empty?
208
- h.store(extra_key, nil)
209
- end
210
- h
183
+ def make_nav
184
+ { I18n.t('nav.home') => '/index.html', I18n.t('pictures.menu') => nil, title => nil }
211
185
  end
212
186
 
213
187
  def gallery_url(key, value, filters = {})
214
188
  filters.store(key, value)
215
189
  filters.store('sort_by', 'datetime')
190
+ filters.store('sort_order', 'asc')
216
191
  hash_to_query(filters)
217
192
  end
218
193
 
219
- def serve_home
220
- content = to_markup('pictures_home', nav: make_nav)
221
- [206, 'text/html',
222
- { content: content, title: title, stylesheets: stylesheets, scripts: scripts }]
223
- rescue KeyError
224
- [404, '', '']
225
- end
226
-
227
- def serve_browse(query)
194
+ def serve_home(query)
228
195
  groups = collect_groups(selector(query), group_by(query), sort_by(query), sort_order(query))
229
- content = to_markup('pictures_browse', nav: make_nav(query), group_key: group_by(query),
230
- filters: selector(query), groups: groups)
196
+ content = to_markup('pictures_home', nav: make_nav, groups: groups,
197
+ group_key: group_by(query),
198
+ filters: selector(query),
199
+ tags: all_tags(selector(query)))
231
200
  [206, 'text/html',
232
201
  { content: content, title: title, stylesheets: stylesheets, scripts: scripts }]
233
202
  rescue KeyError
@@ -240,7 +209,8 @@ module Intranet
240
209
  " viewer_close: '#{I18n.t('pictures.viewer.close')}',\n" \
241
210
  " viewer_zoom: '#{I18n.t('pictures.viewer.zoom')}',\n" \
242
211
  " viewer_previous: '#{I18n.t('pictures.viewer.previous')}',\n" \
243
- " viewer_next: '#{I18n.t('pictures.viewer.next')}' };"]
212
+ " viewer_next: '#{I18n.t('pictures.viewer.next')}',\n" \
213
+ " viewer_toggle_caption: '#{I18n.t('pictures.viewer.toggle_caption')}' };"]
244
214
  end
245
215
 
246
216
  ##########################################################################
@@ -8,7 +8,7 @@ module Intranet
8
8
  NAME = 'intranet-pictures'
9
9
 
10
10
  # The version of the gem, according to semantic versionning.
11
- VERSION = '2.1.0'
11
+ VERSION = '3.0.0.rc1'
12
12
 
13
13
  # The URL of the gem homepage.
14
14
  HOMEPAGE_URL = 'https://rubygems.org/gems/intranet-pictures'
@@ -1,35 +1,28 @@
1
1
  %section
2
2
  = to_markup 'title_and_breadcrumb', {title: title, nav: nav}
3
-
4
- - recent_groups.each do |recent|
5
- %h3= I18n.t('pictures.recents.' + recent[:group_key])
3
+ %h4 Tags
4
+ - if tags.empty?
5
+ %p
6
+ %em= I18n.t("pictures.no_result_found")
7
+ - else
8
+ %div#tags
9
+ - tags.each do |tag_name|
10
+ %button.tag{ type: 'button', onclick: 'filterByTag("' + tag_name + '");' }
11
+ = tag_name
12
+ %h4= I18n.t("pictures.nav.#{group_key}")
13
+ - if groups.empty?
14
+ %p
15
+ %em= I18n.t("pictures.no_result_found")
16
+ - else
6
17
  %ul.groups
7
- - recent[:groups].each do |group_name|
18
+ - groups.each do |group_name|
8
19
  %li{ title: group_name }
9
- %a{ onclick: 'openImagesGallery("' + gallery_url(recent[:group_key], group_name) + '");' }
20
+ %a{ onclick: 'openImagesGallery("' + gallery_url(group_key, group_name, filters) + '");' }
10
21
  %figure
11
- %div{ style: 'background-image: url("api/group_thumbnail?' + recent[:group_key] + '=' + group_name + '")' }
22
+ %div{ style: 'background-image: url("api/group_thumbnail?' + group_key + '=' + group_name + '")' }
12
23
  %figcaption
13
24
  = group_name
14
- - group_brief = api_group_brief({ recent[:group_key] => group_name })
15
- - unless group_brief.empty?
16
- %br
17
- %em= group_brief
18
- - unless recent[:see_more_url].empty?
19
- %p.see_more
20
- %a{ href: recent[:see_more_url] }= I18n.t('pictures.see_more')
21
-
22
- - all_groups.each do |section|
23
- %h3= I18n.t('pictures.browse_by.' + section[:group_key])
24
- %ul.groups.wide
25
- - section[:groups].each do |group_name|
26
- %li{ title: group_name }
27
- %a{ href: section[:url_prefix] + '&' + section[:group_key] + '=' + group_name }
28
- %figure
29
- %div{ style: 'background-image: url("api/group_thumbnail?' + section[:group_key] + '=' + group_name + '")' }
30
- %figcaption
31
- = group_name
32
- - group_brief = api_group_brief({ section[:group_key] => group_name })
25
+ - group_brief = api_group_brief({ group_key => group_name })
33
26
  - unless group_brief.empty?
34
27
  %br
35
28
  %em= group_brief
@@ -4,12 +4,13 @@
4
4
  en:
5
5
  pictures:
6
6
  menu: 'Pictures'
7
- see_more: 'See more results'
7
+ no_result_found: 'No result found'
8
8
  viewer:
9
9
  close: 'Close'
10
10
  zoom: 'Zoom'
11
11
  previous: 'Previous image'
12
12
  next: 'Next image'
13
+ toggle_caption: 'Toggle caption/info pannel'
13
14
  nav:
14
15
  event: 'Events'
15
16
  city: 'Cities'
@@ -17,17 +18,3 @@ en:
17
18
  country: 'Countries'
18
19
  album: 'Albums'
19
20
  category: 'Categories'
20
- recents:
21
- event: 'Latest events'
22
- city: 'Cities visited recently'
23
- region: 'Regions visited recently'
24
- country: 'Countries visited recently'
25
- album: 'Latest pictures albums'
26
- category: 'Categories updated recently'
27
- browse_by:
28
- event: 'Browse pictures by event'
29
- city: 'Browse pictures by city'
30
- region: 'Browse pictures by region'
31
- country: 'Browse pictures by country'
32
- album: 'Browse pictures by album'
33
- category: 'Browse pictures by category'
@@ -4,12 +4,13 @@
4
4
  fr:
5
5
  pictures:
6
6
  menu: 'Photos'
7
- see_more: 'Afficher plus de résultats'
7
+ no_result_found: 'Aucun résultat à afficher'
8
8
  viewer:
9
9
  close: 'Fermer'
10
10
  zoom: 'Agrandir'
11
11
  previous: 'Image précédente'
12
12
  next: 'Image suivante'
13
+ toggle_caption: 'Afficher/Masquer les détails'
13
14
  nav:
14
15
  event: 'Événements'
15
16
  city: 'Villes'
@@ -17,17 +18,3 @@ fr:
17
18
  country: 'Pays'
18
19
  album: 'Albums'
19
20
  category: 'Catégories'
20
- recents:
21
- event: 'Événements récents'
22
- city: 'Dernières villes visitées'
23
- region: 'Dernières régions visitées'
24
- country: 'Derniers pays visités'
25
- album: 'Albums photos récents'
26
- category: 'Catégories mises à jour récemment'
27
- browse_by:
28
- event: 'Parcourir les images par événement'
29
- city: 'Parcourir les images par ville'
30
- region: 'Parcourir les images par région'
31
- country: 'Parcourir les images par pays'
32
- album: 'Parcourir les images par album'
33
- category: 'Parcourir les images par catégorie'
@@ -12,43 +12,144 @@ import PhotoSwipeDynamicCaption from './photoswipe/photoswipe-dynamic-caption-pl
12
12
  // Import internationalization support
13
13
  import i18n from './../i18n.js';
14
14
 
15
- function convertDate(iso8601_date) {
16
- const d = new Date(iso8601_date);
17
- const str = d.toLocaleDateString() + ' ' + d.toLocaleTimeString().replace(/(\d{2}):(\d{2}):(\d{2})/, '$1h$2');
18
- return str;
15
+ class PhotoSwipePictureGallery {
16
+ constructor(items) {
17
+ // Create PhotoSwipe Lightbox
18
+ const lightboxOptions = {
19
+ dataSource: items,
20
+ pswpModule: PhotoSwipe,
21
+ bgOpacity: 0.95,
22
+ closeOnVerticalDrag: false,
23
+ closeTitle: i18n.viewer_close + ' (Esc)',
24
+ zoomTitle: i18n.viewer_zoom + ' (z)',
25
+ arrowPrevTitle: i18n.viewer_previous,
26
+ arrowNextTitle: i18n.viewer_next,
27
+ };
28
+ this.lightbox = new PhotoSwipeLightbox(lightboxOptions);
29
+
30
+ // Initialize caption plugin
31
+ const captionPluginOptions = {
32
+ type: 'below',
33
+ mobileLayoutBreakpoint: 800,
34
+ captionContent: (slide) => {
35
+ return this._captionContent(slide)
36
+ },
37
+ };
38
+ this.captionPlugin = new PhotoSwipeDynamicCaption(this.lightbox, captionPluginOptions);
39
+
40
+ // Add custom 'Info' button, see https://photoswipe.com/v5/docs/adding-custom-buttons/
41
+ // Info menu button
42
+ const infoButton = {
43
+ name: 'info',
44
+ title: i18n.viewer_toggle_caption,
45
+ order: 15, // Insert button between zoom & close buttons
46
+ isButton: true,
47
+ html: {
48
+ isCustomSVG: true,
49
+ inner: '<path d="M7 16a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" id="pswp__icn-info"/>'
50
+ + '<path fill="currentColor" d="M17 15h-2v6h2z"/>'
51
+ + '<path fill="currentColor" d="M17 11h-2v2h2z"/>',
52
+ outlineID: 'pswp__icn-info',
53
+ },
54
+ onClick: (ev, el, pswp) => {
55
+ this._toggleInfoPannel(ev, el, pswp);
56
+ },
57
+ };
58
+ this.lightbox.on('uiRegister', () => {
59
+ this.lightbox.pswp.ui.registerElement(infoButton);
60
+ });
61
+
62
+ this.lightbox.on('calcSlideSize', (e) => {
63
+ /* When using mobile layout for caption, hide the Info button since the caption is fixed
64
+ * at the bottom. */
65
+ if (this.captionPlugin.useMobileLayout()) {
66
+ document.querySelector('.pswp__button--info').style.display = 'none';
67
+ } else {
68
+ document.querySelector('.pswp__button--info').style.display = '';
69
+ }
70
+ });
71
+ }
72
+
73
+ // Open gallery at the given slide index (starting at 0)
74
+ open(index) {
75
+ this.lightbox.init();
76
+ this.lightbox.loadAndOpen(index);
77
+ }
78
+
79
+ // Switch between regular caption (below image) and info pannel (aside)
80
+ _toggleInfoPannel(ev, el, pswp) {
81
+ if (this.captionPlugin.options.type === 'below') {
82
+ this.captionPlugin.options.type = 'aside';
83
+ } else if (this.captionPlugin.options.type === 'aside') {
84
+ this.captionPlugin.options.type = 'below';
85
+ }
86
+ /* Force updating size of all PhotoSwipe elements. This will trigger the 'calcSlideSize' event
87
+ * on each loaded slide, causing update of the caption text (see handler above). */
88
+ this.lightbox.pswp.updateSize(true);
89
+ }
90
+
91
+ _convertDate(iso8601_date) {
92
+ const d = new Date(iso8601_date);
93
+ const str = d.toLocaleDateString() + ' ' + d.toLocaleTimeString().replace(/(\d{2}):(\d{2}):(\d{2})/, '$1h$2');
94
+ return str;
95
+ }
96
+
97
+ // Get caption content, according to current image & caption layout
98
+ _captionContent(slide) {
99
+ const slideTitle = slide.data.title;
100
+ const slideDateTime = this._convertDate(slide.data.datetime, true);
101
+
102
+ if (this.captionPlugin.options.type === 'aside' && !this.captionPlugin.useMobileLayout()) {
103
+ var caption = '<strong>' + slideTitle + '</strong><hr />' +
104
+ '<p class="pswp__caption__exif pswp__caption__exif_datetime">' + slideDateTime + '</p>';
105
+ if (slide.data.artist) {
106
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_author">' + slide.data.artist + '</p>';
107
+ }
108
+ if (slide.data.event) {
109
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_event">' + slide.data.event + '</p>';
110
+ }
111
+ if (slide.data.city || slide.data.country) {
112
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_location">';
113
+ if (slide.data.city) { caption += slide.data.city; }
114
+ if (slide.data.city && slide.data.country) { caption += ', '; }
115
+ if (slide.data.country) { caption += slide.data.country; }
116
+ caption += '</p>';
117
+ }
118
+ if (slide.data.model) {
119
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_camera">' + slide.data.model + '</p>';
120
+ }
121
+ if (slide.data.focallength) {
122
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_focal">' + slide.data.focallength + '</p>';
123
+ }
124
+ if (slide.data.fnumber) {
125
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_fstop">' + slide.data.fnumber + '</p>';
126
+ }
127
+ if (slide.data.exposure) {
128
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_shutter">' + slide.data.exposure + '</p>';
129
+ }
130
+ if (slide.data.isospeed) {
131
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_iso">' + 'ISO ' + slide.data.isospeed + '</p>';
132
+ }
133
+ if (slide.data.tags) {
134
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_tags">' + slide.data.tags.join(", ") + '</p>';
135
+ }
136
+ return caption;
137
+ } else {
138
+ return '<strong>' + slideDateTime + '</strong> &mdash; ' + slideTitle;
139
+ }
140
+ }
19
141
  }
20
142
 
21
143
  function createImageGallery(json) {
22
144
  var items = [];
23
145
  for (let i = 0; i < json.length; i++) {
24
- const imageUrl = 'api/picture?id=' + json[i].id;
25
- const imageTitle = '<strong>' + convertDate(json[i].datetime, true) + '</strong> &mdash; ' + json[i].title;
26
- items.push({ src: imageUrl, width: json[i].width, height: json[i].height, title: imageTitle});
27
- }
28
-
29
- // Create PhotoSwipe Lightbox
30
- const lightboxOptions = {
31
- dataSource: items,
32
- pswpModule: PhotoSwipe,
33
- bgOpacity: 0.95,
34
- closeOnVerticalDrag: false,
35
- closeTitle: i18n.viewer_close + ' (Esc)',
36
- zoomTitle: i18n.viewer_zoom + ' (z)',
37
- arrowPrevTitle: i18n.viewer_previous,
38
- arrowNextTitle: i18n.viewer_next,
39
- };
40
- const lightbox = new PhotoSwipeLightbox(lightboxOptions);
41
-
42
- // Initialize caption plugin
43
- const captionPluginOptions = {
44
- type: 'below',
45
- captionContent: (slide) => { return slide.data.title || ''; }
46
- };
47
- const captionPlugin = new PhotoSwipeDynamicCaption(lightbox, captionPluginOptions);
48
-
49
- // Open gallery
50
- lightbox.init();
51
- lightbox.loadAndOpen(0); // start at first slide
146
+ var imageData = json[i];
147
+ imageData.src = 'api/picture?id=' + json[i].id; // Add 'src' field required by PhotoSwipe
148
+ items.push(imageData);
149
+ }
150
+
151
+ const gallery = new PhotoSwipePictureGallery(items);
152
+ gallery.open(0); // start at first slide
52
153
  }
53
154
 
54
155
  // Export this function so that it may be called from HTML
@@ -58,3 +159,26 @@ function openImagesGallery(selectors) {
58
159
  .then(data => createImageGallery(data));
59
160
  }
60
161
  window.openImagesGallery = openImagesGallery;
162
+
163
+ // Export this function so that it may be called from HTML
164
+ function filterByTag(tagName) {
165
+ const urlParams = new URLSearchParams(window.location.search);
166
+ const activeTag = urlParams.get('tags');
167
+ if (activeTag == tagName) {
168
+ urlParams.delete('tags');
169
+ } else {
170
+ urlParams.set('tags', tagName);
171
+ }
172
+ window.location.search = urlParams.toString();
173
+ }
174
+ window.filterByTag = filterByTag;
175
+
176
+ /* Code executed after page load */
177
+ const urlParams = new URLSearchParams(window.location.search);
178
+ const activeTag = urlParams.get('tags');
179
+ const buttons = document.getElementById('tags').getElementsByTagName('button');
180
+ for (let i = 0; i < buttons.length; i++) {
181
+ if (buttons[i].innerText == activeTag) {
182
+ buttons[i].classList.add('active');
183
+ }
184
+ }