intranet-pictures 2.2.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: 6e5e318561ea69bdaed8eaf38cf8d1349220835196596acc6ba65640c2deac52
4
- data.tar.gz: ebc612ab80afffc3c9f70a3ea0b2da47467006b91854a0e7f7c05227a2c64a2d
3
+ metadata.gz: 42e09da26c3eecce0bdf0c37f9f39ae4db140b850c594677f87672d64ae90a23
4
+ data.tar.gz: fd202aa0c01b91752021116f3bf455497e153a32ce49a4d4b9ce59649b5916fa
5
5
  SHA512:
6
- metadata.gz: fc2992a96129bf8b0e8a0e79753a9833541a10b875bddc9893cf5dec74e595dcf5693fd28f5b5c4b771725fb0bb64f14ebc43efb87855959c780efa19e4a822f
7
- data.tar.gz: 26b461521d1b71a98a37f0e1848a3cae417eb79ccc8ba19faeaafbbc94a38143fa00871e53118417923b3a9bd1326520ae57492f732fa9fcc9f7ee68b0f35a6f
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
@@ -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.2.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,7 +4,7 @@
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'
@@ -18,17 +18,3 @@ en:
18
18
  country: 'Countries'
19
19
  album: 'Albums'
20
20
  category: 'Categories'
21
- recents:
22
- event: 'Latest events'
23
- city: 'Cities visited recently'
24
- region: 'Regions visited recently'
25
- country: 'Countries visited recently'
26
- album: 'Latest pictures albums'
27
- category: 'Categories updated recently'
28
- browse_by:
29
- event: 'Browse pictures by event'
30
- city: 'Browse pictures by city'
31
- region: 'Browse pictures by region'
32
- country: 'Browse pictures by country'
33
- album: 'Browse pictures by album'
34
- category: 'Browse pictures by category'
@@ -4,7 +4,7 @@
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'
@@ -18,17 +18,3 @@ fr:
18
18
  country: 'Pays'
19
19
  album: 'Albums'
20
20
  category: 'Catégories'
21
- recents:
22
- event: 'Événements récents'
23
- city: 'Dernières villes visitées'
24
- region: 'Dernières régions visitées'
25
- country: 'Derniers pays visités'
26
- album: 'Albums photos récents'
27
- category: 'Catégories mises à jour récemment'
28
- browse_by:
29
- event: 'Parcourir les images par événement'
30
- city: 'Parcourir les images par ville'
31
- region: 'Parcourir les images par région'
32
- country: 'Parcourir les images par pays'
33
- album: 'Parcourir les images par album'
34
- category: 'Parcourir les images par catégorie'
@@ -108,11 +108,11 @@ class PhotoSwipePictureGallery {
108
108
  if (slide.data.event) {
109
109
  caption += '<p class="pswp__caption__exif pswp__caption__exif_event">' + slide.data.event + '</p>';
110
110
  }
111
- if (slide.data.city || slide.data.region) {
111
+ if (slide.data.city || slide.data.country) {
112
112
  caption += '<p class="pswp__caption__exif pswp__caption__exif_location">';
113
113
  if (slide.data.city) { caption += slide.data.city; }
114
- if (slide.data.city && slide.data.region) { caption += ', '; }
115
- if (slide.data.region) { caption += slide.data.region; }
114
+ if (slide.data.city && slide.data.country) { caption += ', '; }
115
+ if (slide.data.country) { caption += slide.data.country; }
116
116
  caption += '</p>';
117
117
  }
118
118
  if (slide.data.model) {
@@ -130,6 +130,9 @@ class PhotoSwipePictureGallery {
130
130
  if (slide.data.isospeed) {
131
131
  caption += '<p class="pswp__caption__exif pswp__caption__exif_iso">' + 'ISO ' + slide.data.isospeed + '</p>';
132
132
  }
133
+ if (slide.data.tags) {
134
+ caption += '<p class="pswp__caption__exif pswp__caption__exif_tags">' + slide.data.tags.join(", ") + '</p>';
135
+ }
133
136
  return caption;
134
137
  } else {
135
138
  return '<strong>' + slideDateTime + '</strong> &mdash; ' + slideTitle;
@@ -156,3 +159,26 @@ function openImagesGallery(selectors) {
156
159
  .then(data => createImageGallery(data));
157
160
  }
158
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
+ }
@@ -3,10 +3,24 @@
3
3
  * Design for the Pictures module of the IntraNet.
4
4
  */
5
5
 
6
- /******************************* Recent groups ********************************/
6
+ /************************************ Tags ************************************/
7
7
 
8
- p.see_more {
9
- text-align: right;
8
+ button {
9
+ background: #ffffff;
10
+ border: 1px solid #eeeff2;
11
+ border-radius: 999em;
12
+ margin: 0px 4px 1em; /* top sides bottom */
13
+ padding: 10px 15px; /* top+bottom sides */
14
+ transition: all 0.2s;
15
+ cursor: pointer;
16
+ }
17
+
18
+ button.active {
19
+ background: #eaeaea;
20
+ }
21
+
22
+ button:hover, button:focus {
23
+ box-shadow: 0px 1px 5px rgba(0,0,0,0.08);
10
24
  }
11
25
 
12
26
  /******************************* Picture groups *******************************/
@@ -145,3 +159,7 @@ ul.groups li:hover, ul.groups li:focus {
145
159
  .pswp .pswp__caption__exif_iso {
146
160
  background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjY2NjY2NjIiBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6TTUuNSA3LjVoMnYtMkg5djJoMlY5SDl2Mkg3LjVWOWgtMlY3LjV6TTE5IDE5SDVMMTkgNXYxNHptLTItMnYtMS41aC01VjE3aDV6Ij48L3BhdGg+PC9zdmc+Cg==);
147
161
  }
162
+
163
+ .pswp .pswp__caption__exif_tags {
164
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjY2NjY2NjIiBkPSJNOS4yNTkgNC41SDQuNTAxdjQuNzU4TDE0Ljc0MyAxOS41bDQuNzU4LTQuNzU4em0yLjEyMS0yLjEyMUEzIDMgMCAwIDAgOS4yNTkgMS41SDEuNTAxdjcuNzU4YTMgMyAwIDAgMCAuODc5IDIuMTIxbDEwLjI0MiAxMC4yNDJhMyAzIDAgMCAwIDQuMjQyIDBsNC43NTgtNC43NThhMyAzIDAgMCAwIDAtNC4yNDJ6TTcuNTAxIDlhMS41IDEuNSAwIDEgMSAwLTMgMS41IDEuNSAwIDEgMSAwIDN6bTAgMCIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+Cg==);
165
+ }
@@ -14,16 +14,6 @@ RSpec.describe Intranet::Pictures::JsonDbProvider do
14
14
  end
15
15
  end
16
16
 
17
- describe '#title' do
18
- before do
19
- @provider = described_class.new(File.join(__dir__, 'sample-db.json'))
20
- end
21
-
22
- it 'should return the gallery title' do
23
- expect(@provider.title).to eql('My Gallery')
24
- end
25
- end
26
-
27
17
  describe '#list_pictures' do
28
18
  before do
29
19
  @provider = described_class.new(File.join(__dir__, 'sample-db.json'))
@@ -32,42 +22,63 @@ RSpec.describe Intranet::Pictures::JsonDbProvider do
32
22
  it 'should return the list of pictures without the uri key' do
33
23
  expect(@provider.list_pictures).to eql(
34
24
  [
35
- { 'datetime' => '2019:07:22 09:41:31', 'author' => 'John Doe', 'location' => 'Paris, France', 'camera' => 'Apple iPhone 11' },
36
- { 'datetime' => '2020:06:19 07:51:05', 'flash' => false, 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan' },
37
- { 'datetime' => '2020:06:20 18:14:09', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'New York, USA' },
38
- { 'datetime' => '2020:06:20 06:09:54', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV' },
25
+ { 'datetime' => '2019:07:22 09:41:31', 'author' => 'John Doe', 'location' => 'Paris, France', 'camera' => 'Apple iPhone 11', 'tags' => ['Modern art', 'Louvre'] },
26
+ { 'datetime' => '2020:06:19 07:51:05', 'flash' => 'false', 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan', 'tags' => ['MoMAK', 'Modern art', 'Cherry blossom'] },
27
+ { 'datetime' => '2020:06:20 18:14:09', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'New York, USA', 'tags' => 'Urban photography' },
28
+ { 'datetime' => '2020:06:20 06:09:54', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV', 'tags' => ['Louvre', 'Pyramid', 'Urban photography'] },
39
29
  { 'datetime' => '2019:07:22 09:45:17', 'author' => 'John Doe', 'location' => 'Tokyo, Japan' }
40
30
  ]
41
31
  )
42
32
  end
43
33
 
44
34
  it 'should return only the pictures matching the given selector' do
35
+ # Select only singular string values
45
36
  selector = { 'author' => 'Jane Doe' }
46
37
  expect(@provider.list_pictures(selector)).to eql(
47
38
  [
48
- { 'datetime' => '2020:06:19 07:51:05', 'flash' => false, 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan' },
49
- { 'datetime' => '2020:06:20 18:14:09', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'New York, USA' },
50
- { 'datetime' => '2020:06:20 06:09:54', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV' }
39
+ { 'datetime' => '2020:06:19 07:51:05', 'flash' => 'false', 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan', 'tags' => ['MoMAK', 'Modern art', 'Cherry blossom'] },
40
+ { 'datetime' => '2020:06:20 18:14:09', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'New York, USA', 'tags' => 'Urban photography' },
41
+ { 'datetime' => '2020:06:20 06:09:54', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV', 'tags' => ['Louvre', 'Pyramid', 'Urban photography'] }
42
+ ]
43
+ )
44
+
45
+ # Select singular or array string values
46
+ selector = { 'tags' => 'Urban photography' }
47
+ expect(@provider.list_pictures(selector)).to eql(
48
+ [
49
+ { 'datetime' => '2020:06:20 18:14:09', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'New York, USA', 'tags' => 'Urban photography' },
50
+ { 'datetime' => '2020:06:20 06:09:54', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV', 'tags' => ['Louvre', 'Pyramid', 'Urban photography'] }
51
+ ]
52
+ )
53
+
54
+ # Multiple selectors are AND'ed together
55
+ selector = { 'tags' => 'Modern art', 'location' => 'Paris, France' }
56
+ expect(@provider.list_pictures(selector)).to eql(
57
+ [
58
+ { 'datetime' => '2019:07:22 09:41:31', 'author' => 'John Doe', 'location' => 'Paris, France', 'camera' => 'Apple iPhone 11', 'tags' => ['Modern art', 'Louvre'] }
51
59
  ]
52
60
  )
53
61
  end
54
62
 
55
63
  context 'given a valid +sort_by+ key' do
56
64
  it 'should sort the pictures by the given key' do
65
+ # Ascending (default) sort order, sort key has only singular string values
57
66
  selector = { 'author' => 'Jane Doe' }
58
67
  expect(@provider.list_pictures(selector, 'location')).to eql(
59
68
  [
60
- { 'datetime' => '2020:06:20 18:14:09', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'New York, USA' },
61
- { 'datetime' => '2020:06:20 06:09:54', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV' },
62
- { 'datetime' => '2020:06:19 07:51:05', 'flash' => false, 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan' }
69
+ { 'datetime' => '2020:06:20 18:14:09', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'New York, USA', 'tags' => 'Urban photography' },
70
+ { 'datetime' => '2020:06:20 06:09:54', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV', 'tags' => ['Louvre', 'Pyramid', 'Urban photography'] },
71
+ { 'datetime' => '2020:06:19 07:51:05', 'flash' => 'false', 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan', 'tags' => ['MoMAK', 'Modern art', 'Cherry blossom'] }
63
72
  ]
64
73
  )
65
74
 
75
+ # Descending sort order
76
+ selector = { 'author' => 'Jane Doe' }
66
77
  expect(@provider.list_pictures(selector, 'datetime', false)).to eql(
67
78
  [
68
- { 'datetime' => '2020:06:20 18:14:09', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'New York, USA' },
69
- { 'datetime' => '2020:06:20 06:09:54', 'flash' => true, 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV' },
70
- { 'datetime' => '2020:06:19 07:51:05', 'flash' => false, 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan' }
79
+ { 'datetime' => '2020:06:20 18:14:09', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'New York, USA', 'tags' => 'Urban photography' },
80
+ { 'datetime' => '2020:06:20 06:09:54', 'flash' => 'true', 'author' => 'Jane Doe', 'location' => 'Paris, France', 'camera' => 'Canon EOS 5D MARK IV', 'tags' => ['Louvre', 'Pyramid', 'Urban photography'] },
81
+ { 'datetime' => '2020:06:19 07:51:05', 'flash' => 'false', 'author' => 'Jane Doe', 'location' => 'Tokyo, Japan', 'tags' => ['MoMAK', 'Modern art', 'Cherry blossom'] }
71
82
  ]
72
83
  )
73
84
  end
@@ -78,6 +89,12 @@ RSpec.describe Intranet::Pictures::JsonDbProvider do
78
89
  expect { @provider.list_pictures({}, 'invalid') }.to raise_error(KeyError)
79
90
  end
80
91
  end
92
+
93
+ context 'given an invalid +sort_by+ key with array values' do
94
+ it 'should raise KeyError' do
95
+ expect { @provider.list_pictures({}, 'tags') }.to raise_error(KeyError)
96
+ end
97
+ end
81
98
  end
82
99
 
83
100
  describe '#picture' do