hermitage 0.0.3 → 0.0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -0
- data/README.md +20 -17
- data/app/assets/javascripts/hermitage.js.coffee +157 -105
- data/lib/generators/hermitage/templates/hermitage.rb +15 -14
- data/lib/hermitage/configurator.rb +31 -0
- data/lib/hermitage/rails_render_core.rb +58 -0
- data/lib/hermitage/version.rb +1 -1
- data/lib/hermitage/view_helpers.rb +3 -35
- data/lib/hermitage.rb +5 -0
- data/spec/features/bottom_panel_spec.rb +2 -2
- data/spec/features/preload_neighbours_spec.rb +76 -0
- data/spec/features/render_gallery_spec.rb +8 -1
- data/spec/lib/hermitage/configurator_spec.rb +42 -0
- data/spec/lib/hermitage/view_helpers_spec.rb +6 -6
- data/spec/spec_helper.rb +5 -1
- metadata +8 -2
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
### 0.0.4.1 ###
|
2
|
+
|
3
|
+
* RailsRenderCore is not a child of ActionView::Base anymore
|
4
|
+
|
5
|
+
### 0.0.4 ###
|
6
|
+
|
7
|
+
* Ability to preload neighbour images
|
8
|
+
* Slide effect added and is used by default
|
9
|
+
* Nice DSL syntax added for configuration
|
10
|
+
|
1
11
|
### 0.0.3 ###
|
2
12
|
|
3
13
|
* *Attention*! In this version some options were renamed: attribute_full_size -> original, attribute_thumbnail -> thumbnail.
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Hermitage
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/ImmaculatePine/hermitage.png?branch=master)](https://travis-ci.org/ImmaculatePine/hermitage)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/ImmaculatePine/hermitage.png)](https://codeclimate.com/github/ImmaculatePine/hermitage)
|
4
5
|
|
5
6
|
Ruby library for generation of image galleries (thumbnails and original images viewer).
|
6
7
|
|
@@ -130,7 +131,7 @@ When you call `render_gallery_for` method Hermitage looks for config with name f
|
|
130
131
|
In the example above Hermitage tries to find :images config because first argument of `render_gallery_for` method was array of Image instances.
|
131
132
|
If there is no proper config :default config is used.
|
132
133
|
|
133
|
-
Hermitage configs are described in config/initializers/hermitage.rb file.
|
134
|
+
Hermitage configs are described in config/initializers/hermitage.rb file. For configuration you can use DSL syntax described below.
|
134
135
|
|
135
136
|
#### Overwriting Defaults
|
136
137
|
|
@@ -138,10 +139,10 @@ You can overwrite :default config. These changes will be applied to all the gall
|
|
138
139
|
|
139
140
|
Uncoment the following lines in config/initializers/hermitage.rb file and make some changes here:
|
140
141
|
|
141
|
-
Hermitage.
|
142
|
-
original
|
143
|
-
thumbnail
|
144
|
-
|
142
|
+
Hermitage.configure :default do
|
143
|
+
original 'image.url(:medium)'
|
144
|
+
thumbnail 'image.url(:small)'
|
145
|
+
end
|
145
146
|
|
146
147
|
Now Hermitage will use `image.url` method with :medium or :small argument to get images for the gallery.
|
147
148
|
|
@@ -169,19 +170,19 @@ Then your config/initializers/hermitage.rb could looks like this:
|
|
169
170
|
|
170
171
|
# Some rules for :default config if needed...
|
171
172
|
|
172
|
-
Hermitage.
|
173
|
-
original
|
174
|
-
thumbnail
|
175
|
-
|
173
|
+
Hermitage.configure :pictures do
|
174
|
+
original 'image_path'
|
175
|
+
thumbnail 'image_path(:small)'
|
176
|
+
end
|
176
177
|
|
177
|
-
Hermitage.
|
178
|
-
original
|
179
|
-
thumbnail
|
180
|
-
list_tag
|
181
|
-
item_tag:
|
182
|
-
list_class
|
183
|
-
item_class
|
184
|
-
|
178
|
+
Hermitage.configure :posts do
|
179
|
+
original 'attachment'
|
180
|
+
thumbnail 'attachment(:tiny)'
|
181
|
+
list_tag :div
|
182
|
+
item_tag :div
|
183
|
+
list_class 'posts'
|
184
|
+
item_class 'post'
|
185
|
+
end
|
185
186
|
|
186
187
|
Now when you write `render_gallery_for @pictures` or `render_gallery_for @posts` Hermitage will automatically choose the proper config.
|
187
188
|
|
@@ -206,6 +207,8 @@ In the example above the darkening will be disabled and both navigation buttons
|
|
206
207
|
You can customize the following parameters:
|
207
208
|
|
208
209
|
* `looped` - set it to false if after the last image the first should not be shown
|
210
|
+
* `preloadNeighbours` - determines if next and previous images should be preloaded
|
211
|
+
* `slideshowEffect` - how to show/hide images (correct values are 'slide' and 'fade'). Default value is 'slide'
|
209
212
|
* `darkening.opacity` - opacity of darkening layer (0 if it should be disabled)
|
210
213
|
* `darkening.styles` - any custom CSS for darkening layer
|
211
214
|
* `navigationButtons.enabled` - are there navigation buttons
|
@@ -7,8 +7,10 @@ root = exports ? this
|
|
7
7
|
# Hermitage options
|
8
8
|
root.hermitage =
|
9
9
|
looped: true
|
10
|
+
preloadNeighbours: true
|
11
|
+
slideshowEffect: 'slide' # or 'fade'
|
10
12
|
|
11
|
-
# Image viewer
|
13
|
+
# Image viewer properties
|
12
14
|
default:
|
13
15
|
styles:
|
14
16
|
zIndex: 10
|
@@ -21,8 +23,7 @@ root.hermitage =
|
|
21
23
|
# Darkening properties
|
22
24
|
darkening:
|
23
25
|
default:
|
24
|
-
attributes:
|
25
|
-
id: 'overlay'
|
26
|
+
attributes: { id: 'overlay' }
|
26
27
|
styles:
|
27
28
|
position: 'absolute'
|
28
29
|
top: 0
|
@@ -30,13 +31,14 @@ root.hermitage =
|
|
30
31
|
width: '100%'
|
31
32
|
height: '100%'
|
32
33
|
backgroundColor: '#000'
|
33
|
-
|
34
|
-
opacity: 0.85 # 0 if you don't want darkening
|
35
34
|
styles: {}
|
35
|
+
opacity: 0.85 # 0 if you don't want darkening
|
36
|
+
|
36
37
|
|
37
38
|
# Navigation buttons' properties
|
38
39
|
navigationButtons:
|
39
40
|
default:
|
41
|
+
attributes: {}
|
40
42
|
styles:
|
41
43
|
position: 'absolute'
|
42
44
|
width: '50px'
|
@@ -54,27 +56,22 @@ root.hermitage =
|
|
54
56
|
|
55
57
|
next:
|
56
58
|
default:
|
57
|
-
attributes:
|
58
|
-
|
59
|
-
styles:
|
60
|
-
right: 0
|
59
|
+
attributes: { id: 'navigation-right' }
|
60
|
+
styles: { right: 0 }
|
61
61
|
styles: {}
|
62
62
|
text: '▶'
|
63
63
|
|
64
64
|
previous:
|
65
65
|
default:
|
66
|
-
attributes:
|
67
|
-
|
68
|
-
styles:
|
69
|
-
left: 0
|
66
|
+
attributes: { id: 'navigation-left' }
|
67
|
+
styles: { left: 0 }
|
70
68
|
styles: {}
|
71
69
|
text: '◀'
|
72
70
|
|
73
71
|
# Close button properties
|
74
72
|
closeButton:
|
75
73
|
default:
|
76
|
-
attributes:
|
77
|
-
id: 'close-button'
|
74
|
+
attributes: { id: 'close-button' }
|
78
75
|
styles:
|
79
76
|
position: 'absolute'
|
80
77
|
top: 0
|
@@ -84,16 +81,15 @@ root.hermitage =
|
|
84
81
|
fontFamily: 'Tahoma,Arial,Helvetica,sans-serif'
|
85
82
|
whiteSpace: 'nowrap'
|
86
83
|
cursor: 'pointer'
|
87
|
-
|
84
|
+
styles: {}
|
88
85
|
enabled: true
|
89
86
|
text: '×'
|
90
|
-
|
87
|
+
|
91
88
|
|
92
89
|
# Current image properties
|
93
90
|
image:
|
94
91
|
default:
|
95
|
-
attributes:
|
96
|
-
class: 'current'
|
92
|
+
attributes: { class: 'current' }
|
97
93
|
styles:
|
98
94
|
cursor: 'pointer'
|
99
95
|
maxWidth: 'none' # Fix the conflict with Twitter Bootstrap
|
@@ -102,8 +98,7 @@ root.hermitage =
|
|
102
98
|
# Bottom panel (for text or anything else)
|
103
99
|
bottomPanel:
|
104
100
|
default:
|
105
|
-
attributes:
|
106
|
-
class: 'bottom-panel'
|
101
|
+
attributes: { class: 'bottom-panel' }
|
107
102
|
styles:
|
108
103
|
position: 'absolute'
|
109
104
|
bottom: 0
|
@@ -112,8 +107,7 @@ root.hermitage =
|
|
112
107
|
|
113
108
|
text:
|
114
109
|
default:
|
115
|
-
attributes:
|
116
|
-
class: 'text'
|
110
|
+
attributes: { class: 'text' }
|
117
111
|
styles:
|
118
112
|
width: '100%'
|
119
113
|
height: '100%'
|
@@ -132,9 +126,6 @@ root.hermitage =
|
|
132
126
|
# Array of images of current gallery
|
133
127
|
images: []
|
134
128
|
|
135
|
-
# Array of texts for current gallery
|
136
|
-
texts: []
|
137
|
-
|
138
129
|
# Timeout before adjustig elements after window resize
|
139
130
|
resizeTimeout: 100
|
140
131
|
|
@@ -153,12 +144,10 @@ root.hermitage =
|
|
153
144
|
|
154
145
|
# Clear old images and texts array
|
155
146
|
hermitage.images.length = 0
|
156
|
-
hermitage.texts.length = 0
|
157
147
|
|
158
148
|
# Create new images and texts array
|
159
149
|
$.each $('a[rel="hermitage"]'), ->
|
160
|
-
|
161
|
-
hermitage.texts.push($(this).attr('title'))
|
150
|
+
addImage($(this).attr('href'), $(this).attr('title'))
|
162
151
|
|
163
152
|
# Set on click handlers to all elements that
|
164
153
|
# have 'hermitage' rel attribute
|
@@ -173,54 +162,89 @@ root.hermitage =
|
|
173
162
|
-> adjustImage(true),
|
174
163
|
hermitage.resizeTimeout
|
175
164
|
|
165
|
+
#
|
166
|
+
# Working with images array
|
167
|
+
#
|
168
|
+
addImage = (source, text) ->
|
169
|
+
image =
|
170
|
+
source: source
|
171
|
+
text: text
|
172
|
+
loaded: false
|
173
|
+
hermitage.images.push(image)
|
174
|
+
|
175
|
+
indexOfImage = (image) ->
|
176
|
+
source = if $(image).prop('tagName') is 'IMG' then $(image).attr('src') else $(image).attr('href')
|
177
|
+
imageObject = (img for img in hermitage.images when img.source is source)[0]
|
178
|
+
hermitage.images.indexOf(imageObject)
|
179
|
+
|
180
|
+
imageAt = (index) -> hermitage.images[index]
|
181
|
+
sourceFor = (index) -> imageAt(index).source
|
182
|
+
textFor = (index) -> imageAt(index).text
|
183
|
+
|
176
184
|
#
|
177
185
|
# Helpers
|
178
186
|
#
|
187
|
+
$.fn.applyStyles = (params, withAnimation, complete = undefined) ->
|
188
|
+
if withAnimation
|
189
|
+
this.animate(params, { duration: hermitage.animationDuration, queue: false, complete: complete })
|
190
|
+
else
|
191
|
+
this.css(params)
|
179
192
|
|
180
193
|
# Place element at the center of screen
|
181
194
|
$.fn.center = (withAnimation = false, width = 0, height = 0, offsetX = 0, offsetY = 0) ->
|
182
195
|
this.css('position', 'absolute')
|
183
196
|
|
184
197
|
width = $(this).outerWidth() if width is 0
|
185
|
-
height = $(this).
|
198
|
+
height = $(this).outerHeight() if height is 0
|
186
199
|
|
187
|
-
|
200
|
+
params =
|
188
201
|
top: "#{Math.max(0, ($(window).height() - height) / 2 + offsetY)}px"
|
189
202
|
left: "#{Math.max(0, ($(window).width() - width) / 2 + offsetX)}px"
|
190
203
|
|
191
|
-
|
192
|
-
this.animate(param, { duration: hermitage.animationDuration, queue: false })
|
193
|
-
else
|
194
|
-
this.css(param)
|
204
|
+
this.applyStyles(params, withAnimation)
|
195
205
|
|
196
206
|
$.fn.setSize = (width, height, withAnimation = false) ->
|
197
207
|
params = { width: width, height: height}
|
198
|
-
|
199
|
-
if withAnimation
|
200
|
-
this.animate(params, { duration: hermitage.animationDuration, queue: false })
|
201
|
-
else
|
202
|
-
this.css(params)
|
208
|
+
this.applyStyles(params, withAnimation)
|
203
209
|
|
204
210
|
$.fn.maximizeLineHeight = (withAnimation = false) ->
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
+
params = { lineHeight: "#{this.outerHeight()}px" }
|
212
|
+
this.applyStyles(params, withAnimation)
|
213
|
+
|
214
|
+
$.fn.showFromRight = (width = 0, height = 0, offsetX = 0, offsetY = 0) ->
|
215
|
+
this.css(left: "#{$(window).width()}px")
|
216
|
+
this.show()
|
217
|
+
this.center(true, width, height, offsetX, offsetY)
|
218
|
+
|
219
|
+
$.fn.showFromLeft = (width = 0, height = 0, offsetX = 0, offsetY = 0) ->
|
220
|
+
this.css(left: "#{- $(this).outerWidth()}px")
|
221
|
+
this.show()
|
222
|
+
this.center(true, width, height, offsetX, offsetY)
|
223
|
+
|
224
|
+
$.fn.hideToRight = (complete = undefined) ->
|
225
|
+
params = { left: "#{$(window).width()}px" }
|
226
|
+
this.applyStyles(params, true, complete)
|
227
|
+
|
228
|
+
$.fn.hideToLeft = (complete = undefined) ->
|
229
|
+
params = { left: "#{- $(this).outerWidth()}px" }
|
230
|
+
this.applyStyles(params, true, complete)
|
211
231
|
|
212
232
|
#
|
213
233
|
# Hermitage logic
|
214
234
|
#
|
215
235
|
|
236
|
+
# Creates base element with attributes and styles from params
|
237
|
+
createElement = (tag, params) ->
|
238
|
+
tag
|
239
|
+
.attr(params.default.attributes)
|
240
|
+
.css(params.default.styles)
|
241
|
+
.css(params.styles)
|
242
|
+
|
216
243
|
# Creates darkening overlay, shows it and sets its click handler
|
217
244
|
createDarkening = ->
|
218
|
-
$('<div>')
|
219
|
-
.attr(hermitage.darkening.default.attributes)
|
220
|
-
.css(hermitage.darkening.default.styles)
|
245
|
+
createElement($('<div>'), hermitage.darkening)
|
221
246
|
.css('opacity', hermitage.darkening.opacity)
|
222
247
|
.css('filter', "alpha(opacity='#{hermitage.darkening.opacity * 100}')")
|
223
|
-
.css(hermitage.darkening.styles)
|
224
248
|
.appendTo($('#hermitage'))
|
225
249
|
.hide()
|
226
250
|
.fadeIn()
|
@@ -228,60 +252,42 @@ createDarkening = ->
|
|
228
252
|
|
229
253
|
# Creates base navigation button and returns it
|
230
254
|
createNavigationButton = ->
|
231
|
-
$('<div>')
|
255
|
+
createElement($('<div>'), hermitage.navigationButtons)
|
232
256
|
.appendTo($('#hermitage'))
|
233
257
|
.hide()
|
234
|
-
.css(hermitage.navigationButtons.default.styles)
|
235
258
|
.maximizeLineHeight()
|
236
|
-
.css(hermitage.navigationButtons.styles)
|
237
259
|
|
238
260
|
# Creates right navigation button and returns it
|
239
261
|
createRightNavigationButton = ->
|
240
|
-
createNavigationButton()
|
241
|
-
.attr(hermitage.navigationButtons.next.default.attributes)
|
242
|
-
.css(hermitage.navigationButtons.next.default.styles)
|
243
|
-
.css(hermitage.navigationButtons.next.styles)
|
262
|
+
createElement(createNavigationButton(), hermitage.navigationButtons.next)
|
244
263
|
.text(hermitage.navigationButtons.next.text)
|
245
264
|
.click(showNextImage)
|
246
265
|
|
247
266
|
# Create left navigation button and returns it
|
248
267
|
createLeftNavigationButton = ->
|
249
|
-
createNavigationButton()
|
250
|
-
.attr(hermitage.navigationButtons.previous.default.attributes)
|
251
|
-
.css(hermitage.navigationButtons.previous.default.styles)
|
252
|
-
.css(hermitage.navigationButtons.previous.styles)
|
268
|
+
createElement(createNavigationButton(), hermitage.navigationButtons.previous)
|
253
269
|
.text(hermitage.navigationButtons.previous.text)
|
254
270
|
.click(showPreviousImage)
|
255
271
|
|
256
272
|
# Creates close button
|
257
273
|
createCloseButton = ->
|
258
|
-
$('<div>')
|
274
|
+
createElement($('<div>'), hermitage.closeButton)
|
259
275
|
.appendTo($('#hermitage'))
|
260
276
|
.hide()
|
261
|
-
.attr(hermitage.closeButton.default.attributes)
|
262
|
-
.css(hermitage.closeButton.default.styles)
|
263
277
|
.text(hermitage.closeButton.text)
|
264
|
-
.css(hermitage.closeButton.styles)
|
265
278
|
.click(closeGallery)
|
266
279
|
|
267
280
|
createBotomPanel = ->
|
268
|
-
bottomPanel = $('<div>')
|
281
|
+
bottomPanel = createElement($('<div>'), hermitage.bottomPanel)
|
269
282
|
.appendTo($('#hermitage'))
|
270
283
|
.hide()
|
271
|
-
.attr(hermitage.bottomPanel.default.attributes)
|
272
|
-
.css(hermitage.bottomPanel.default.styles)
|
273
|
-
.css(hermitage.bottomPanel.styles)
|
274
284
|
|
275
|
-
|
285
|
+
createElement($('<div>'), hermitage.bottomPanel.text)
|
276
286
|
.appendTo(bottomPanel)
|
277
|
-
.attr(hermitage.bottomPanel.text.default.attributes)
|
278
|
-
.css(hermitage.bottomPanel.text.default.styles)
|
279
|
-
.css(hermitage.bottomPanel.text.styles)
|
280
287
|
|
281
288
|
# Shows original image of the chosen one
|
282
289
|
openGallery = (image) ->
|
283
290
|
$('#hermitage')
|
284
|
-
.css('z-index', hermitage.zIndex)
|
285
291
|
.empty()
|
286
292
|
.show()
|
287
293
|
|
@@ -294,12 +300,9 @@ openGallery = (image) ->
|
|
294
300
|
showImage(indexOfImage(image))
|
295
301
|
|
296
302
|
# Shows image with specified index from images array
|
297
|
-
showImage = (index) ->
|
298
|
-
img = $('<img />')
|
299
|
-
.attr(
|
300
|
-
.css(hermitage.image.default.styles)
|
301
|
-
.attr('src', hermitage.images[index])
|
302
|
-
.css(hermitage.image.styles)
|
303
|
+
showImage = (index, direction = undefined) ->
|
304
|
+
img = createElement($('<img />'), hermitage.image)
|
305
|
+
.attr('src', sourceFor(index))
|
303
306
|
.hide()
|
304
307
|
.appendTo($('#hermitage'))
|
305
308
|
|
@@ -309,7 +312,8 @@ showImage = (index) ->
|
|
309
312
|
else
|
310
313
|
showPreviousImage()
|
311
314
|
|
312
|
-
adjustImage(false, img)
|
315
|
+
adjustImage(false, img, direction)
|
316
|
+
preloadNeighboursFor(index)
|
313
317
|
|
314
318
|
# Shows next image
|
315
319
|
showNextImage = ->
|
@@ -317,11 +321,12 @@ showNextImage = ->
|
|
317
321
|
if current.length is 1
|
318
322
|
index = indexOfImage(current)
|
319
323
|
return unless canShowNextAfter(index)
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
324
|
+
|
325
|
+
direction = if hermitage.slideshowEffect is 'slide' then 'left' else undefined
|
326
|
+
hideCurrentImage(direction)
|
327
|
+
|
328
|
+
direction = if hermitage.slideshowEffect is 'slide' then 'right' else undefined
|
329
|
+
showImage nextIndexAfter(index), direction
|
325
330
|
|
326
331
|
# Shows previous image
|
327
332
|
showPreviousImage = ->
|
@@ -329,18 +334,30 @@ showPreviousImage = ->
|
|
329
334
|
if current.length is 1
|
330
335
|
index = indexOfImage(current)
|
331
336
|
return unless canShowPreviousBefore(index)
|
332
|
-
|
333
|
-
if
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
+
|
338
|
+
direction = if hermitage.slideshowEffect is 'slide' then 'right' else undefined
|
339
|
+
hideCurrentImage(direction)
|
340
|
+
|
341
|
+
direction = if hermitage.slideshowEffect is 'slide' then 'left' else undefined
|
342
|
+
showImage previousIndexBefore(index), direction
|
337
343
|
|
338
344
|
# Hides current image
|
339
|
-
|
345
|
+
# Arguments:
|
346
|
+
# * direction - how to hide the image. It can apply options:
|
347
|
+
# * undefined - just fade out
|
348
|
+
# * 'right' - move to the right bound of screen
|
349
|
+
# * 'left' - move to the left bound of screen
|
350
|
+
hideCurrentImage = (direction = undefined) ->
|
340
351
|
current = $('img.current')
|
341
352
|
if current.length is 1
|
342
|
-
|
343
|
-
|
353
|
+
complete = -> current.remove()
|
354
|
+
|
355
|
+
if direction is 'right'
|
356
|
+
current.hideToRight(complete)
|
357
|
+
else if direction is 'left'
|
358
|
+
current.hideToLeft(complete)
|
359
|
+
else
|
360
|
+
current.fadeOut hermitage.animationDuration, complete
|
344
361
|
|
345
362
|
# Starts fade out animation and clears Hermitage at the end of animation
|
346
363
|
closeGallery = ->
|
@@ -355,7 +372,11 @@ closeGallery = ->
|
|
355
372
|
# Attributes:
|
356
373
|
# * `withAnimation` - boolean value determines if adjusting should be animated
|
357
374
|
# * `image` - currently opened image. It is optional argument and can be evaluated by the method itself.
|
358
|
-
|
375
|
+
# * direction - how to show the image. It can apply options:
|
376
|
+
# * undefined - just fade in
|
377
|
+
# * 'right' - move from the right bound of screen
|
378
|
+
# * 'left' - move from the left bound of screen
|
379
|
+
adjustImage = (withAnimation = false, image = undefined, direction = undefined) ->
|
359
380
|
|
360
381
|
if image is undefined
|
361
382
|
image = $('#hermitage img.current')
|
@@ -364,15 +385,16 @@ adjustImage = (withAnimation = false, image = undefined) ->
|
|
364
385
|
index = indexOfImage(image)
|
365
386
|
|
366
387
|
# Wait until source image is loaded
|
367
|
-
|
368
|
-
|
388
|
+
loadImage sourceFor(index), ->
|
389
|
+
imageAt(index).loaded = true
|
390
|
+
|
369
391
|
# Offset for image position
|
370
392
|
offsetY = 0
|
371
393
|
|
372
394
|
maxWidth = $(window).width() - $('#navigation-left').outerWidth() - $('#navigation-right').outerWidth()
|
373
395
|
maxHeight = $(window).height()
|
374
396
|
|
375
|
-
text =
|
397
|
+
text = textFor(index)
|
376
398
|
|
377
399
|
if text
|
378
400
|
offsetY = - $('#hermitage .bottom-panel').outerHeight() / 2
|
@@ -396,7 +418,13 @@ adjustImage = (withAnimation = false, image = undefined) ->
|
|
396
418
|
image
|
397
419
|
.setSize(this.width * scale, this.height * scale, withAnimation)
|
398
420
|
.center(withAnimation, this.width * scale, this.height * scale, 0, offsetY)
|
399
|
-
|
421
|
+
|
422
|
+
if direction is 'right'
|
423
|
+
image.showFromRight(this.width * scale, this.height * scale, 0, offsetY)
|
424
|
+
else if direction is 'left'
|
425
|
+
image.showFromLeft(this.width * scale, this.height * scale, 0, offsetY)
|
426
|
+
else
|
427
|
+
image.fadeIn(hermitage.animationDuration)
|
400
428
|
|
401
429
|
adjustNavigationButtons(withAnimation, image)
|
402
430
|
adjustCloseButton(withAnimation, image)
|
@@ -414,9 +442,7 @@ adjustNavigationButtons = (withAnimation, current) ->
|
|
414
442
|
next.maximizeLineHeight(withAnimation)
|
415
443
|
|
416
444
|
# Show or hide buttons
|
417
|
-
|
418
445
|
currentIndex = indexOfImage(current)
|
419
|
-
|
420
446
|
duration = hermitage.animationDuration
|
421
447
|
|
422
448
|
if canShowPreviousBefore(currentIndex)
|
@@ -452,10 +478,6 @@ adjustBottomPanel = (withAnimation) ->
|
|
452
478
|
|
453
479
|
panel.fadeIn(hermitage.animationDuration)
|
454
480
|
|
455
|
-
indexOfImage = (image) ->
|
456
|
-
href = if $(image).prop('tagName') is 'IMG' then $(image).attr('src') else $(image).attr('href')
|
457
|
-
hermitage.images.indexOf(href)
|
458
|
-
|
459
481
|
canShowNextAfter = (index) ->
|
460
482
|
if index < hermitage.images.length - 1
|
461
483
|
true
|
@@ -468,6 +490,36 @@ canShowPreviousBefore = (index) ->
|
|
468
490
|
else
|
469
491
|
hermitage.looped
|
470
492
|
|
493
|
+
preloadNeighboursFor = (index) ->
|
494
|
+
return unless hermitage.preloadNeighbours
|
495
|
+
|
496
|
+
nextIndex = nextIndexAfter(index)
|
497
|
+
previousIndex = previousIndexBefore(index)
|
498
|
+
|
499
|
+
if canShowNextAfter(index) and not imageAt(nextIndex).loaded
|
500
|
+
loadImage sourceFor(nextIndex), ->
|
501
|
+
imageAt(nextIndex).loaded = true
|
502
|
+
|
503
|
+
if canShowPreviousBefore(index) and not imageAt(previousIndex).loaded
|
504
|
+
loadImage sourceFor(previousIndex), ->
|
505
|
+
imageAt(previousIndex).loaded = true
|
506
|
+
|
507
|
+
loadImage = (source, complete) ->
|
508
|
+
$('<img />').attr('src', source).load(complete)
|
509
|
+
|
510
|
+
nextIndexAfter = (index) ->
|
511
|
+
if index < hermitage.images.length - 1
|
512
|
+
index + 1
|
513
|
+
else
|
514
|
+
0
|
515
|
+
|
516
|
+
previousIndexBefore = (index) ->
|
517
|
+
if index > 0
|
518
|
+
index - 1
|
519
|
+
else
|
520
|
+
hermitage.images.length - 1
|
521
|
+
|
522
|
+
|
471
523
|
# Initialize gallery on page load
|
472
524
|
$(document).ready(hermitage.init)
|
473
525
|
$(document).on('page:load', hermitage.init)
|
@@ -3,17 +3,18 @@
|
|
3
3
|
# Default config is used as base options hash for every gallery.
|
4
4
|
# You can configure any of its options and they will be applied for every rendering.
|
5
5
|
#
|
6
|
-
# Hermitage.
|
7
|
-
# original
|
8
|
-
# thumbnail
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
6
|
+
# Hermitage.configure :default do
|
7
|
+
# original 'file.url'
|
8
|
+
# thumbnail 'file.url(:thumbnail)'
|
9
|
+
# title nil
|
10
|
+
# list_tag :ul
|
11
|
+
# item_tag :li
|
12
|
+
# list_class 'thumbnails'
|
13
|
+
# item_class 'span4'
|
14
|
+
# link_class 'thumbnail'
|
15
|
+
# image_class nil
|
16
|
+
# each_slice nil
|
17
|
+
# end
|
17
18
|
|
18
19
|
# Also you can create your own configs that will be merged with default config to overwrite default options.
|
19
20
|
#
|
@@ -25,6 +26,6 @@
|
|
25
26
|
#
|
26
27
|
# All available options are listed in default config above.
|
27
28
|
#
|
28
|
-
# Hermitage.
|
29
|
-
|
30
|
-
#
|
29
|
+
# Hermitage.configure :images do
|
30
|
+
#
|
31
|
+
# end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Hermitage
|
2
|
+
|
3
|
+
class Configurator
|
4
|
+
|
5
|
+
Defaults.constants.each do |c|
|
6
|
+
define_method c.downcase do |value|
|
7
|
+
@config.merge!({ c.downcase.to_sym => value })
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def respond_to?(method_name)
|
12
|
+
Defaults.constants.include? method_name.upcase.to_sym || super
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(config_name, &block)
|
16
|
+
Hermitage.configs[config_name] ||= {}
|
17
|
+
@config = Hermitage.configs[config_name]
|
18
|
+
self.instance_eval(&block) if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns full options hash for specified objects and options.
|
22
|
+
# It chooses config accoring to the class name of objects in passed array
|
23
|
+
# and merges default options with the chosen config and with passed options hash.
|
24
|
+
def self.options_for(objects, options = {})
|
25
|
+
config_name = objects.first.class.to_s.pluralize.underscore.to_sym if defined?(Rails) && !objects.empty?
|
26
|
+
config = Hermitage.configs[config_name] || Hermitage.configs[:default]
|
27
|
+
Hermitage.configs[:default].merge(config).merge(options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Hermitage
|
2
|
+
|
3
|
+
# This class performs all the rendering logic for Rails apps
|
4
|
+
class RailsRenderCore
|
5
|
+
|
6
|
+
def initialize(objects, options = {})
|
7
|
+
@objects = objects
|
8
|
+
@options = Configurator.options_for(objects, options)
|
9
|
+
@template = ActionView::Base.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Renders gallery markup
|
13
|
+
def render
|
14
|
+
# Initialize the resulting tag
|
15
|
+
tag = ''
|
16
|
+
|
17
|
+
# Slice objects into separate lists
|
18
|
+
lists = slice_objects
|
19
|
+
|
20
|
+
# Render each list into `tag` variable
|
21
|
+
lists.each do |list|
|
22
|
+
items = list.collect { |item| render_link_for(item) }
|
23
|
+
tag << render_content_tag_for(items)
|
24
|
+
end
|
25
|
+
|
26
|
+
tag.html_safe
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Slices objects into separate lists if it's necessary
|
32
|
+
def slice_objects
|
33
|
+
unless @options[:each_slice]
|
34
|
+
[@objects]
|
35
|
+
else
|
36
|
+
@objects.each_slice(@options[:each_slice]).to_a
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Renders link to the specific image in a gallery
|
41
|
+
def render_link_for(item)
|
42
|
+
original_path = eval("item.#{@options[:original]}")
|
43
|
+
thumbnail_path = eval("item.#{@options[:thumbnail]}")
|
44
|
+
title = @options[:title] ? eval("item.#{@options[:title]}") : nil
|
45
|
+
image = @template.image_tag(thumbnail_path, class: @options[:image_class])
|
46
|
+
@template.link_to(image, original_path, rel: 'hermitage', class: @options[:link_class], title: title)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Renders items into content tag
|
50
|
+
def render_content_tag_for(items)
|
51
|
+
@template.content_tag(@options[:list_tag], class: @options[:list_class]) do
|
52
|
+
items.collect { |item| @template.concat(@template.content_tag(@options[:item_tag], item, class: @options[:item_class])) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/lib/hermitage/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'hermitage/rails_render_core' if defined? Rails
|
2
|
+
|
1
3
|
module Hermitage
|
2
4
|
|
3
5
|
module ViewHelpers
|
@@ -17,41 +19,7 @@ module Hermitage
|
|
17
19
|
# Config names are formed by the class name of the first element in array.
|
18
20
|
#
|
19
21
|
def render_gallery_for(objects, options = {})
|
20
|
-
|
21
|
-
config_name = objects.first.class.to_s.pluralize.underscore.to_sym if defined?(Rails) && !objects.empty?
|
22
|
-
config = Hermitage.configs.include?(config_name) ? config_name : :default
|
23
|
-
|
24
|
-
# Merge default options with the chosen config and with passed options
|
25
|
-
options = Hermitage.configs[:default].merge(Hermitage.configs[config]).merge(options)
|
26
|
-
|
27
|
-
# Create array of all list tags
|
28
|
-
lists = unless options[:each_slice]
|
29
|
-
[objects]
|
30
|
-
else
|
31
|
-
objects.each_slice(options[:each_slice]).to_a
|
32
|
-
end
|
33
|
-
|
34
|
-
# The resulting tag
|
35
|
-
tag = ''
|
36
|
-
|
37
|
-
# Render each list into `tag` variable
|
38
|
-
lists.each do |list|
|
39
|
-
# Array of items in current list
|
40
|
-
items = list.collect do |item|
|
41
|
-
original_path = eval("item.#{options[:original]}")
|
42
|
-
thumbnail_path = eval("item.#{options[:thumbnail]}")
|
43
|
-
title = options[:title] ? eval("item.#{options[:title]}") : nil
|
44
|
-
image = image_tag(thumbnail_path, class: options[:image_class])
|
45
|
-
link_to(image, original_path, rel: 'hermitage', class: options[:link_class], title: title)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Convert these items into content tag string
|
49
|
-
tag << content_tag(options[:list_tag], class: options[:list_class]) do
|
50
|
-
items.collect { |item| concat(content_tag(options[:item_tag], item, class: options[:item_class])) }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
tag.html_safe
|
22
|
+
RailsRenderCore.new(objects, options).render if defined? Rails
|
55
23
|
end
|
56
24
|
|
57
25
|
end
|
data/lib/hermitage.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'hermitage/version'
|
2
2
|
require 'hermitage/defaults'
|
3
|
+
require 'hermitage/configurator'
|
3
4
|
require 'hermitage/railtie' if defined? Rails
|
4
5
|
require 'hermitage/engine' if defined? Rails
|
5
6
|
|
@@ -10,4 +11,8 @@ module Hermitage
|
|
10
11
|
# Hash of configs presets
|
11
12
|
self.configs = { default: Hermitage::Defaults.to_hash() }
|
12
13
|
|
14
|
+
def self.configure(config_name, &block)
|
15
|
+
configurator = Configurator.new(config_name, &block)
|
16
|
+
end
|
17
|
+
|
13
18
|
end
|
@@ -3,14 +3,14 @@ require 'features_helper'
|
|
3
3
|
|
4
4
|
describe 'bottom_panel', type: :feature, js: true do
|
5
5
|
before(:each) do
|
6
|
-
Hermitage.
|
6
|
+
Hermitage.configure :default do title 'description' end
|
7
7
|
visit images_path
|
8
8
|
page.first('a[rel="hermitage"]').click
|
9
9
|
page.should have_css('img.current')
|
10
10
|
end
|
11
11
|
|
12
12
|
# I don't want any influence of this config to another tests
|
13
|
-
after(:each) {
|
13
|
+
after(:each) { reset_configs }
|
14
14
|
|
15
15
|
it 'has bottom panel' do
|
16
16
|
page.should have_css('#hermitage .bottom-panel')
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'preload_neighbours', type: :feature, js: true do
|
4
|
+
|
5
|
+
def clickAtImage(index)
|
6
|
+
page.first("a[href='/assets/#{index}-full.png']").click
|
7
|
+
sleep(1) # We want wait some time while animations and images preloading
|
8
|
+
end
|
9
|
+
|
10
|
+
def images
|
11
|
+
evaluate_script('hermitage.images')
|
12
|
+
end
|
13
|
+
|
14
|
+
shared_examples 'preloader' do
|
15
|
+
it 'preloads proper images' do
|
16
|
+
loaded = images().collect { |image| image['loaded'] }
|
17
|
+
loaded.should == expected
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before(:each) { visit images_path }
|
22
|
+
|
23
|
+
context 'preload enabled' do
|
24
|
+
context 'loop enabled' do
|
25
|
+
let(:expected) { Array.new(3, true) }
|
26
|
+
|
27
|
+
context '1st image' do
|
28
|
+
before(:each) { clickAtImage(0) }
|
29
|
+
it_behaves_like 'preloader'
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'middle image' do
|
33
|
+
before(:each) { clickAtImage(1) }
|
34
|
+
it_behaves_like 'preloader'
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'last image' do
|
38
|
+
before(:each) { clickAtImage(2) }
|
39
|
+
it_behaves_like 'preloader'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'loop disabled' do
|
44
|
+
before(:each) { evaluate_script('hermitage.looped = false') }
|
45
|
+
|
46
|
+
context '1st image' do
|
47
|
+
before(:each) { clickAtImage(0) }
|
48
|
+
let(:expected) { [true, true, false] }
|
49
|
+
it_behaves_like 'preloader'
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'middle image' do
|
53
|
+
before(:each) { clickAtImage(1) }
|
54
|
+
let(:expected) { [true, true, true] }
|
55
|
+
it_behaves_like 'preloader'
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'last image' do
|
59
|
+
before(:each) { clickAtImage(2) }
|
60
|
+
let(:expected) { [false, true, true] }
|
61
|
+
it_behaves_like 'preloader'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'preload disabled' do
|
67
|
+
before(:each) do
|
68
|
+
evaluate_script('hermitage.preloadNeighbours = false')
|
69
|
+
clickAtImage(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
let(:expected) { [false, true, false] }
|
73
|
+
it_behaves_like 'preloader'
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -13,7 +13,14 @@ describe 'render gallery', type: :feature, js: true do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'fills images array' do
|
16
|
-
evaluate_script('hermitage.images')
|
16
|
+
images = evaluate_script('hermitage.images')
|
17
|
+
expected = Array.new(3) do |i|
|
18
|
+
{
|
19
|
+
'source' => "/assets/#{i}-full.png",
|
20
|
+
'loaded' => false
|
21
|
+
}
|
22
|
+
end
|
23
|
+
images.should == expected
|
17
24
|
end
|
18
25
|
|
19
26
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hermitage::Configurator do
|
4
|
+
after(:each) { reset_configs }
|
5
|
+
|
6
|
+
describe '.new' do
|
7
|
+
it 'can create new config' do
|
8
|
+
Hermitage::Configurator.new(:new_config)
|
9
|
+
Hermitage.configs.keys.should == [:default, :new_config]
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can change existing config' do
|
13
|
+
Hermitage::Configurator.new(:default) do
|
14
|
+
title 'description'
|
15
|
+
end
|
16
|
+
|
17
|
+
Hermitage.configs[:default][:title].should == 'description'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#respond_to?' do
|
22
|
+
it 'responds to methods corresponing to options' do
|
23
|
+
configurator = Hermitage::Configurator.new(:new_config)
|
24
|
+
Hermitage::Defaults.constants.each do |c|
|
25
|
+
configurator.should respond_to(c.downcase.to_sym)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.options_for' do
|
31
|
+
it 'merges default config with objects config and with options hash' do
|
32
|
+
Hermitage.configure :images do
|
33
|
+
title 'image_title'
|
34
|
+
list_class 'image-list'
|
35
|
+
end
|
36
|
+
Image = Class.new
|
37
|
+
objects = [Image.new]
|
38
|
+
result = Hermitage::Configurator.options_for(objects, { title: 'title' })
|
39
|
+
result.should == Hermitage::Defaults.to_hash.merge({ list_class: 'image-list', title: 'title' })
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -4,7 +4,7 @@ require 'dummy/app/models/dummy'
|
|
4
4
|
describe Hermitage::ViewHelpers, type: :helper do
|
5
5
|
|
6
6
|
# Reset Hermitage configs because they should not be shared among specs
|
7
|
-
|
7
|
+
after(:each) { reset_configs }
|
8
8
|
|
9
9
|
let(:template) { ActionView::Base.new }
|
10
10
|
|
@@ -52,7 +52,7 @@ describe Hermitage::ViewHelpers, type: :helper do
|
|
52
52
|
|
53
53
|
context 'with configs' do
|
54
54
|
|
55
|
-
before(:each) { Hermitage.
|
55
|
+
before(:each) { Hermitage.configure :dummy_images do list_class 'images-thumbnails' end }
|
56
56
|
|
57
57
|
let(:expected) { '<ul class="images-thumbnails"><li class="span4"><a class="thumbnail" href="/assets/0-full.png" rel="hermitage"><img alt="0 thumbnail" src="/assets/0-thumbnail.png" /></a></li><li class="span4"><a class="thumbnail" href="/assets/1-full.png" rel="hermitage"><img alt="1 thumbnail" src="/assets/1-thumbnail.png" /></a></li></ul>' }
|
58
58
|
subject { template.render_gallery_for images }
|
@@ -61,10 +61,10 @@ describe Hermitage::ViewHelpers, type: :helper do
|
|
61
61
|
context 'with overwritten defaults' do
|
62
62
|
|
63
63
|
before(:each) do
|
64
|
-
Hermitage.
|
65
|
-
list_class
|
66
|
-
item_class
|
67
|
-
|
64
|
+
Hermitage.configure :default do
|
65
|
+
list_class 'default-thumbnails'
|
66
|
+
item_class 'span3'
|
67
|
+
end
|
68
68
|
end
|
69
69
|
|
70
70
|
let(:expected) { '<ul class="images-thumbnails"><li class="span3"><a class="thumbnail" href="/assets/0-full.png" rel="hermitage"><img alt="0 thumbnail" src="/assets/0-thumbnail.png" /></a></li><li class="span3"><a class="thumbnail" href="/assets/1-full.png" rel="hermitage"><img alt="1 thumbnail" src="/assets/1-thumbnail.png" /></a></li></ul>' }
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hermitage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-09-
|
12
|
+
date: 2013-09-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -209,8 +209,10 @@ files:
|
|
209
209
|
- lib/generators/hermitage/install_generator.rb
|
210
210
|
- lib/generators/hermitage/templates/hermitage.rb
|
211
211
|
- lib/hermitage.rb
|
212
|
+
- lib/hermitage/configurator.rb
|
212
213
|
- lib/hermitage/defaults.rb
|
213
214
|
- lib/hermitage/engine.rb
|
215
|
+
- lib/hermitage/rails_render_core.rb
|
214
216
|
- lib/hermitage/railtie.rb
|
215
217
|
- lib/hermitage/version.rb
|
216
218
|
- lib/hermitage/view_helpers.rb
|
@@ -266,6 +268,7 @@ files:
|
|
266
268
|
- spec/features/bottom_panel_spec.rb
|
267
269
|
- spec/features/engine_spec.rb
|
268
270
|
- spec/features/navigation_spec.rb
|
271
|
+
- spec/features/preload_neighbours_spec.rb
|
269
272
|
- spec/features/render_gallery_spec.rb
|
270
273
|
- spec/features/resize_spec.rb
|
271
274
|
- spec/features/scale_spec.rb
|
@@ -273,6 +276,7 @@ files:
|
|
273
276
|
- spec/features/viewer_spec.rb
|
274
277
|
- spec/features_helper.rb
|
275
278
|
- spec/generators/install_spec.rb
|
279
|
+
- spec/lib/hermitage/configurator_spec.rb
|
276
280
|
- spec/lib/hermitage/defaults_spec.rb
|
277
281
|
- spec/lib/hermitage/railtie_spec.rb
|
278
282
|
- spec/lib/hermitage/view_helpers_spec.rb
|
@@ -357,6 +361,7 @@ test_files:
|
|
357
361
|
- spec/features/bottom_panel_spec.rb
|
358
362
|
- spec/features/engine_spec.rb
|
359
363
|
- spec/features/navigation_spec.rb
|
364
|
+
- spec/features/preload_neighbours_spec.rb
|
360
365
|
- spec/features/render_gallery_spec.rb
|
361
366
|
- spec/features/resize_spec.rb
|
362
367
|
- spec/features/scale_spec.rb
|
@@ -364,6 +369,7 @@ test_files:
|
|
364
369
|
- spec/features/viewer_spec.rb
|
365
370
|
- spec/features_helper.rb
|
366
371
|
- spec/generators/install_spec.rb
|
372
|
+
- spec/lib/hermitage/configurator_spec.rb
|
367
373
|
- spec/lib/hermitage/defaults_spec.rb
|
368
374
|
- spec/lib/hermitage/railtie_spec.rb
|
369
375
|
- spec/lib/hermitage/view_helpers_spec.rb
|