jekyll-pig 0.0.1

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.
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: []