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 +4 -4
- data/lib/intranet/pictures/json_db_provider.rb +39 -27
- data/lib/intranet/pictures/responder.rb +43 -74
- data/lib/intranet/pictures/version.rb +1 -1
- data/lib/intranet/resources/haml/pictures_home.haml +18 -25
- data/lib/intranet/resources/locales/en.yml +1 -15
- data/lib/intranet/resources/locales/fr.yml +1 -15
- data/lib/intranet/resources/www/jpictures.js +29 -3
- data/lib/intranet/resources/www/style.css +21 -3
- data/spec/intranet/pictures/json_db_provider_spec.rb +40 -23
- data/spec/intranet/pictures/responder_spec.rb +122 -176
- data/spec/intranet/pictures/sample-db.json +13 -9
- metadata +4 -5
- data/lib/intranet/resources/haml/pictures_browse.haml +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42e09da26c3eecce0bdf0c37f9f39ae4db140b850c594677f87672d64ae90a23
|
4
|
+
data.tar.gz: fd202aa0c01b91752021116f3bf455497e153a32ce49a4d4b9ce59649b5916fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,
|
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
|
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
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
#
|
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
|
-
|
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?
|
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
|
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
|
39
|
-
#
|
40
|
-
#
|
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,
|
43
|
+
def initialize(provider, defaults, in_menu = true)
|
55
44
|
@provider = provider
|
56
|
-
@
|
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$}
|
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
|
93
|
+
else super
|
107
94
|
end
|
108
95
|
end
|
109
96
|
|
110
|
-
#
|
111
|
-
# @return [String] The title of the Pictures
|
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
|
-
|
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
|
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
|
137
|
-
|
138
|
-
|
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
|
-
|
146
|
-
|
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
|
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
|
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
|
180
|
-
@
|
181
|
-
|
182
|
-
|
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
|
202
|
-
|
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('
|
230
|
-
|
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
|
@@ -1,35 +1,28 @@
|
|
1
1
|
%section
|
2
2
|
= to_markup 'title_and_breadcrumb', {title: title, nav: nav}
|
3
|
-
|
4
|
-
-
|
5
|
-
%
|
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
|
-
-
|
18
|
+
- groups.each do |group_name|
|
8
19
|
%li{ title: group_name }
|
9
|
-
%a{ onclick: 'openImagesGallery("' + gallery_url(
|
20
|
+
%a{ onclick: 'openImagesGallery("' + gallery_url(group_key, group_name, filters) + '");' }
|
10
21
|
%figure
|
11
|
-
%div{ style: 'background-image: url("api/group_thumbnail?' +
|
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({
|
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
|
-
|
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
|
-
|
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.
|
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.
|
115
|
-
if (slide.data.
|
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> — ' + 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
|
-
|
6
|
+
/************************************ Tags ************************************/
|
7
7
|
|
8
|
-
|
9
|
-
|
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
|