jekyll-pig 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jekyll-pig.rb +262 -0
  3. metadata +59 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f4318788681e364b49c60f4f16347407579a5d08521b14a256acfe2da615f95f
4
+ data.tar.gz: 98d542da59f0bb43be00272838ea42b320a1ca8aa077139d50f49fbba5fc44df
5
+ SHA512:
6
+ metadata.gz: efa93dac3c338d0721b00c42a6dbd28b2984ca3de5f5987e9548410f9b89622aee71a3d01c2c2aa63acc9401d4af046c542e00dd507b0a5b216be49795f6a948
7
+ data.tar.gz: 0a831bb70e85c46164d9d55804886746259cf3e3ebb6d1e5668760ab0582abd5760e81fb417aac65b296a068183b0371351acfc3461063204890e08bc7499b6c
data/lib/jekyll-pig.rb ADDED
@@ -0,0 +1,262 @@
1
+
2
+ require 'fileutils'
3
+ require 'json'
4
+ require 'mini_magick'
5
+
6
+ module JekyllPig
7
+
8
+ class SourceGallery
9
+ def initialize(path, name)
10
+ @path = path
11
+ @name = name
12
+ end
13
+ def to_s
14
+ "gallery #{@name} at #{@path}"
15
+ end
16
+ def path
17
+ @path
18
+ end
19
+ def name
20
+ @name
21
+ end
22
+ end
23
+
24
+ class JekyllPig < Jekyll::Generator
25
+
26
+ @@image_cache = {}
27
+
28
+ @@pig_min_js = '!function(t){"use strict";var i,e,s=(e=!(i=[]),{add:function(t){i.length||window.addEventListener("resize",n),i.push(t)},disable:function(){window.removeEventListener("resize",n)},reEnable:function(){window.addEventListener("resize",n)}});function n(){e||(e=!0,window.requestAnimationFrame?window.requestAnimationFrame(o):setTimeout(o,66))}function o(){i.forEach(function(t){t()}),e=!1}function a(t,i){return this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings={containerId:"pig",scroller:window,classPrefix:"pig",figureTagName:"figure",spaceBetweenImages:8,transitionSpeed:500,primaryImageBufferHeight:1e3,secondaryImageBufferHeight:300,thumbnailSize:20,urlForSize:function(t,i){return"/img/"+i+"/"+t},onClickHandler:function(t){},getMinAspectRatio:function(t){return t<=640?2:t<=1280?4:t<=1920?5:6},getImageSize:function(t){return t<=640?100:t<=1920?250:500}},function(t,i){for(var e in i)i.hasOwnProperty(e)&&(t[e]=i[e])}(this.settings,i||{}),this.container=document.getElementById(this.settings.containerId),this.container||console.error("Could not find element with ID "+this.settings.containerId),this.scroller=this.settings.scroller,this.images=this._parseImageData(t),function(t,i,e){var s="#"+t+" { position: relative;}."+i+"-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}."+i+"-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: "+e/1e3+"s ease opacity; -webkit-transition: "+e/1e3+"s ease opacity;}."+i+"-figure img."+i+"-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}."+i+"-figure img."+i+"-loaded { opacity: 1;}",n=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",o.styleSheet?o.styleSheet.cssText=s:o.appendChild(document.createTextNode(s)),n.appendChild(o)}(this.settings.containerId,this.settings.classPrefix,this.settings.transitionSpeed),this}function r(t,i,e){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=i,this.pig=e,this.classNames={figure:e.settings.classPrefix+"-figure",thumbnail:e.settings.classPrefix+"-thumbnail",loaded:e.settings.classPrefix+"-loaded"},this}a.prototype._getTransitionTimeout=function(){return 1.5*this.settings.transitionSpeed},a.prototype._getTransitionString=function(){return this.isTransitioning?this.settings.transitionSpeed/1e3+"s transform ease":"none"},a.prototype._recomputeMinAspectRatio=function(){var t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),null!==t&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1},a.prototype._parseImageData=function(t){var s=[];return t.forEach(function(t,i){var e=new r(t,i,this);s.push(e)}.bind(this)),s},a.prototype._computeLayout=function(){var s=parseInt(this.container.clientWidth),n=[],o=0,a=0,r=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(function(){this.isTransitioning=!1},this._getTransitionTimeout()));var h=this._getTransitionString();[].forEach.call(this.images,function(t,i){if(r+=parseFloat(t.aspectRatio),n.push(t),r>=this.minAspectRatio||i+1===this.images.length){r=Math.max(r,this.minAspectRatio);var e=(s-this.settings.spaceBetweenImages*(n.length-1))/r;n.forEach(function(t){var i=e*t.aspectRatio;t.style={width:parseInt(i),height:parseInt(e),translateX:o,translateY:a,transition:h},o+=i+this.settings.spaceBetweenImages}.bind(this)),n=[],r=0,a+=parseInt(e)+this.settings.spaceBetweenImages,o=0}}.bind(this)),this.totalHeight=a-this.settings.spaceBetweenImages},a.prototype._doLayout=function(){this.container.style.height=this.totalHeight+"px";var t="up"===this.scrollDirection?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,i="down"===this.scrollDirection?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,e=function(t){for(var i=0;isNaN(t.offsetTop)||(i+=t.offsetTop),t=t.offsetParent;);return i}(this.container),s=this.scroller===window?window.innerHeight:this.scroller.offsetHeight,n=this.latestYOffset-e-t,o=this.latestYOffset-e+s+i;this.images.forEach(function(t){t.style.translateY+t.style.height<n||t.style.translateY>o?t.hide():t.load()}.bind(this))},a.prototype._getOnScroll=function(){var i=this;return function(){var t=i.scroller===window?window.pageYOffset:i.scroller.scrollTop;i.previousYOffset=i.latestYOffset||t,i.latestYOffset=t,i.scrollDirection=i.latestYOffset>i.previousYOffset?"down":"up",i.inRAF||(i.inRAF=!0,window.requestAnimationFrame(function(){i._doLayout(),i.inRAF=!1}))}},a.prototype.enable=function(){return this.onScroll=this._getOnScroll(),this.scroller.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),s.add(function(){this.lastWindowWidth=this.scroller===window?window.innerWidth:this.scroller.offsetWidth,this._computeLayout(),this._doLayout()}.bind(this)),this},a.prototype.disable=function(){return this.scroller.removeEventListener("scroll",this.onScroll),s.disable(),this},r.prototype.load=function(){this.existsOnPage=!0,this._updateStyles(),this.pig.container.appendChild(this.getElement()),setTimeout(function(){this.existsOnPage&&(this.thumbnail||(this.thumbnail=new Image,this.thumbnail.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.thumbnailSize),this.thumbnail.className=this.classNames.thumbnail,this.thumbnail.onload=function(){this.thumbnail&&(this.thumbnail.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.thumbnail)),this.fullImage||(this.fullImage=new Image,this.fullImage.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth)),this.fullImage.onload=function(){this.fullImage&&(this.fullImage.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.fullImage)))}.bind(this),100)},r.prototype.hide=function(){this.getElement()&&(this.thumbnail&&(this.thumbnail.src="",this.getElement().removeChild(this.thumbnail),delete this.thumbnail),this.fullImage&&(this.fullImage.src="",this.getElement().removeChild(this.fullImage),delete this.fullImage)),this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1},r.prototype.getElement=function(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this.element.addEventListener("click",function(){this.pig.settings.onClickHandler(this.filename)}.bind(this)),this._updateStyles()),this.element},r.prototype._updateStyles=function(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=this.style.width+"px",this.getElement().style.height=this.style.height+"px",this.getElement().style.transform="translate3d("+this.style.translateX+"px,"+this.style.translateY+"px, 0)"},"function"==typeof define&&define.amd?define([],function(){return a}):"undefined"!=typeof module&&module.exports?module.exports=a:t.Pig=a}("undefined"!=typeof window?window:this);'
29
+
30
+ def full_size_html(gallery_name, name, date, prev_url, next_url)
31
+ "---\n" \
32
+ "layout: post\n" \
33
+ "title: #{name}\n" \
34
+ "date: #{date.strftime("%Y-%m-%d %H:%M:%S")}\n" \
35
+ "permalink: /assets/html/#{gallery_name}/#{name}.html\n" \
36
+ "exclude: true\n" \
37
+ "---\n" \
38
+ "<div><a href=\"#{prev_url}\" style=\"display:inline;\">prev</a><a href=\"#{next_url}\" style=\"display:inline; float:right\">next</a></div>\n" \
39
+ "<img src=\"{{site.baseurl}}/assets/img/#{gallery_name}/1024/#{name}\"/>\n"
40
+ end
41
+
42
+ def gallery_html(id, image_data)
43
+ "<div id='#{id}_pig'></div>\n" \
44
+ "<script src='{{site.baseurl}}/assets/js/pig.min.js'></script>\n" \
45
+ "<script>\n" \
46
+ "var #{id}_pig = new Pig(\n" \
47
+ " #{image_data.to_json()},\n" \
48
+ " {\n" \
49
+ " containerId: '#{id}_pig',\n" \
50
+ " classPrefix: '#{id}_pig',\n" \
51
+ " urlForSize: function(filename, size) {\n" \
52
+ " return '{{site.baseurl}}/assets/img/#{id}/' + size + '/' + filename;\n" \
53
+ " },\n" \
54
+ " onClickHandler: function(filename) {\n" \
55
+ " window.location.href = '{{site.baseurl}}/assets/html/#{id}/' + filename + '.html';\n" \
56
+ " }\n" \
57
+ " }\n" \
58
+ ").enable();\n" \
59
+ "</script>"
60
+ end
61
+
62
+ def image_html_url(gallery_name, image_name)
63
+ "/assets/html/#{gallery_name}/#{image_name}.html"
64
+ end
65
+
66
+ #read the image data from the _includes folder
67
+ def get_image_data(gallery_name)
68
+ image_data = []
69
+ #read image_data if existing
70
+ if File.exists?(File.join(@data_path, "#{gallery_name}.json"))
71
+ File.open(File.join(@data_path, "#{gallery_name}.json"), 'r') { |file|
72
+ #get array of image data (drop 'var imageData = ' and ';')
73
+ image_data = JSON.parse(file.read)
74
+ }
75
+ end
76
+ image_data
77
+ end
78
+
79
+ #read images that require processing from gallery
80
+ def get_images(gallery_path)
81
+ patterns = ['*.jpg', '*.jpeg', '*.png'].map { |ext| File.join(gallery_path, ext) }
82
+ Dir.glob(patterns).map { |path| File.basename(path) }
83
+ end
84
+
85
+ def get_image(gallery_path, image_name)
86
+ image = @@image_cache[File.join(gallery_path, image_name)]
87
+ if image == nil
88
+ image = MiniMagick::Image.open(File.join(gallery_path, image_name))
89
+ @@image_cache[File.join(gallery_path, image_name)] = image
90
+ end
91
+ image
92
+ end
93
+
94
+ def get_image_date(gallery_path, image_name)
95
+ image_date = nil
96
+ begin
97
+ image = get_image(gallery_path, image_name)
98
+ exif_date = image.exif['DateTimeOriginal']
99
+ if exif_date == nil
100
+ #no exif date, try to get from file name
101
+ image_date = Time.strptime(image_name, "%Y-%m-%d")
102
+ else
103
+ #try to get the image date from exif
104
+ image_date = Time.strptime(exif_date, "%Y:%m:%d %H:%M:%S")
105
+ end
106
+ rescue
107
+ #get the date from file if possible
108
+ image_date = File.mtime(File.join(gallery_path, image_name))
109
+ end
110
+ image_date
111
+ end
112
+
113
+ def get_previous_url(image_data, gallery_name, image_name)
114
+ index = image_data.index { |data| data['filename'] == image_name }
115
+ index = index - 1
116
+ if index < 0
117
+ index = image_data.length - 1
118
+ end
119
+ image_html_url(gallery_name, image_data[index]['filename'])
120
+ end
121
+
122
+ def get_next_url(image_data, gallery_name, image_name)
123
+ index = image_data.index { |data| data['filename'] == image_name }
124
+ index = index + 1
125
+ if index >= image_data.length
126
+ index = 0
127
+ end
128
+ image_html_url(gallery_name, image_data[index]['filename'])
129
+ end
130
+
131
+ #create thumbnails and fullsize image assets, and create full size html page for a given image
132
+ def process_image(image_data, gallery_id, gallery_path, image_name)
133
+ #puts "jekyll-pig: processing " << image_name
134
+ #create thumbs
135
+ [1024, 500, 250, 100, 20].each { |size|
136
+ size_out_path = File.join(@img_path, gallery_id, size.to_s)
137
+ resized_img_path = File.join(size_out_path, image_name)
138
+ if not File.exists? resized_img_path
139
+ image = get_image(gallery_path, image_name)
140
+ image.resize("x" + size.to_s)
141
+ FileUtils.mkdir_p size_out_path unless File.exists? size_out_path
142
+ image.write(resized_img_path)
143
+ end
144
+ }
145
+ full_size_html_path = File.join(@html_path, gallery_id, image_name + ".html")
146
+ #create full size html if it doesn't exist
147
+ if not File.exists? full_size_html_path
148
+ #get image date
149
+ image_date = get_image_date(gallery_path, image_name)
150
+ #create full size html text
151
+ full_size_html = full_size_html(gallery_id, image_name, image_date,
152
+ get_previous_url(image_data, gallery_id, image_name),
153
+ get_next_url(image_data, gallery_id, image_name))
154
+ File.open(full_size_html_path, 'w') { |file|
155
+ file.write(full_size_html)
156
+ }
157
+ end
158
+ end
159
+
160
+ def get_paths
161
+ @assets_path = File.join(@site.source, "assets")
162
+ @js_path = File.join(@assets_path, "js")
163
+ @data_path = File.join(@site.source, "_data")
164
+ @img_path = File.join(@assets_path, "img")
165
+ @html_path = File.join(@assets_path, "html")
166
+ @includes_path = File.join(@site.source, "_includes")
167
+ end
168
+
169
+ def get_galleries
170
+ galleries = []
171
+ config_galleries = Jekyll.configuration({})['galleries']
172
+ if config_galleries != nil
173
+ config_galleries.each do |gallery|
174
+ full_path = File.join(@site.source, gallery['path'])
175
+ if File.directory?(full_path)
176
+ galleries << SourceGallery.new(full_path, gallery['name'])
177
+ end
178
+ end
179
+ else
180
+ default_gallery_path = File.join(@site.source, 'gallery')
181
+ if File.directory?(default_gallery_path)
182
+ galleries << SourceGallery.new(default_gallery_path, 'gallery')
183
+ end
184
+ end
185
+ galleries
186
+ end
187
+
188
+ def make_output_paths
189
+ FileUtils.mkdir_p @assets_path unless File.exists? @assets_path
190
+ FileUtils.mkdir_p @js_path unless File.exists? @js_path
191
+ FileUtils.mkdir_p @img_path unless File.exists? @img_path
192
+ FileUtils.mkdir_p @html_path unless File.exists? @html_path
193
+ FileUtils.mkdir_p @includes_path unless File.exists? @includes_path
194
+ FileUtils.mkdir_p @data_path unless File.exists? @data_path
195
+ end
196
+
197
+ def augment_image_data(gallery, image_data, images)
198
+ images.each do |image_name|
199
+ #append data to image_data array if it's not already there
200
+ if not image_data.any? { |data| data['filename'] == image_name }
201
+ #get image date
202
+ image_date = get_image_date(gallery.path, image_name)
203
+ image = get_image(gallery.path, image_name)
204
+ image_data <<
205
+ {
206
+ 'datetime' => image_date.to_s,
207
+ 'filename' => image_name,
208
+ 'aspectRatio' => image.width.to_f / image.height
209
+ }
210
+ end
211
+ end
212
+ end
213
+
214
+ def generate(site)
215
+ @site = site
216
+ get_paths()
217
+ make_output_paths()
218
+ galleries = get_galleries()
219
+ galleries.each do |gallery|
220
+
221
+ #make gallery specific html and image output paths
222
+ html_output_path = File.join(@html_path, gallery.name)
223
+ FileUtils.mkdir_p html_output_path unless File.exists? html_output_path
224
+ img_output_path = File.join(@img_path, gallery.name)
225
+ FileUtils.mkdir_p img_output_path unless File.exists? img_output_path
226
+
227
+ #write pig.min.js to js path
228
+ if not File.exists? File.join(@js_path, 'pig.min.js')
229
+ File.open(File.join(@js_path, 'pig.min.js'), 'w') { |file| file.write(@@pig_min_js) }
230
+ end
231
+
232
+ #get image data from _data
233
+ image_data = get_image_data(gallery.name)
234
+
235
+ #get images from gallery
236
+ images = get_images(gallery.path)
237
+
238
+ #add any additional images to image_data
239
+ augment_image_data(gallery, image_data, images)
240
+
241
+ #sort image data
242
+ image_data = image_data.sort_by { |data| data['datetime'] }
243
+
244
+ #process images
245
+ images.each do |image_name|
246
+ #create thumbs, full size, and html assets for each image
247
+ process_image(image_data, gallery.name, gallery.path, image_name)
248
+ end
249
+
250
+ #write image_data
251
+ File.open(File.join(@data_path, "#{gallery.name}.json"), 'w') { |file|
252
+ file.write(image_data.to_json)
253
+ }
254
+
255
+ #save this gallery's includable content
256
+ File.open(File.join(@includes_path, "#{gallery.name}.html"), 'w') { |file|
257
+ file.write(gallery_html(gallery.name, image_data))
258
+ }
259
+ end
260
+ end
261
+ end
262
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-pig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Colin Holzman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mini_magick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.10'
27
+ description: Uses ImageMagick and pig.js to create progressive image galleries for
28
+ Jekyll pages
29
+ email: me@colinholzman.xyz
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/jekyll-pig.rb
35
+ homepage: https://github.com/clnhlzmn/jekyll-pig
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.7.6
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Jekyll image gallery generator
59
+ test_files: []