piccle 0.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/NOTES.md +69 -0
- data/README.md +175 -0
- data/Rakefile +8 -0
- data/agpl-3.0.md +660 -0
- data/assets/css/default.css +397 -0
- data/assets/css/normalize.css +427 -0
- data/assets/icons/android-chrome-192x192.png +0 -0
- data/assets/icons/android-chrome-512x512.png +0 -0
- data/assets/icons/apple-touch-icon.png +0 -0
- data/assets/icons/favicon-16x16.png +0 -0
- data/assets/icons/favicon-32x32.png +0 -0
- data/assets/icons/favicon.ico +0 -0
- data/bin/console +14 -0
- data/bin/piccle +355 -0
- data/bin/setup +8 -0
- data/db/migrations/001_create_photos.rb +15 -0
- data/db/migrations/002_update_photos.rb +14 -0
- data/db/migrations/003_create_keywords_and_join_table.rb +14 -0
- data/db/migrations/004_add_focal_length.rb +7 -0
- data/db/migrations/005_create_locations.rb +20 -0
- data/js-renderer/handlebars.min-v4.7.6.js +29 -0
- data/js-renderer/renderer.js +93 -0
- data/lib/piccle.rb +52 -0
- data/lib/piccle/config.rb +136 -0
- data/lib/piccle/database.rb +33 -0
- data/lib/piccle/dstk_service.rb +64 -0
- data/lib/piccle/extractor.rb +128 -0
- data/lib/piccle/js_renderer.rb +37 -0
- data/lib/piccle/models/keyword.rb +6 -0
- data/lib/piccle/models/location.rb +11 -0
- data/lib/piccle/models/photo.rb +211 -0
- data/lib/piccle/parser.rb +230 -0
- data/lib/piccle/quilt_generator.rb +30 -0
- data/lib/piccle/renderer.rb +175 -0
- data/lib/piccle/streams.rb +2 -0
- data/lib/piccle/streams/base_stream.rb +56 -0
- data/lib/piccle/streams/camera_stream.rb +35 -0
- data/lib/piccle/streams/date_stream.rb +95 -0
- data/lib/piccle/streams/event_stream.rb +73 -0
- data/lib/piccle/streams/keyword_stream.rb +24 -0
- data/lib/piccle/streams/location_stream.rb +57 -0
- data/lib/piccle/template_helpers.rb +79 -0
- data/lib/piccle/version.rb +3 -0
- data/lib/tasks/development.rake +38 -0
- data/piccle.gemspec +43 -0
- data/templates/_breadcrumbs.handlebars.slim +16 -0
- data/templates/_footer.handlebars.slim +2 -0
- data/templates/_header.handlebars.slim +36 -0
- data/templates/_navigation.handlebars.slim +16 -0
- data/templates/_substream.handlebars.slim +17 -0
- data/templates/feed.atom.slim +29 -0
- data/templates/index.html.handlebars.slim +36 -0
- data/templates/show.html.handlebars.slim +64 -0
- metadata +340 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rmagick'
|
2
|
+
|
3
|
+
# Generates an image quilt, ideal for OpenGraph previews.
|
4
|
+
module Piccle
|
5
|
+
class QuiltGenerator
|
6
|
+
# Generates a quilt of the given images - up to 9.
|
7
|
+
def self.generate_for(photo_paths)
|
8
|
+
photo_paths = photo_paths.first(9)
|
9
|
+
image_list = Magick::ImageList.new(*photo_paths)
|
10
|
+
image_list.montage do |conf|
|
11
|
+
conf.border_width = 0
|
12
|
+
conf.geometry = "200x200+0+0"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a tuple of [width, height] output dimensions. When we stitch a quilt together each square is 200px,
|
17
|
+
# but variable numbers of images can lead to quilts of different sizes. OpenGraph tags should include this
|
18
|
+
# size information.
|
19
|
+
def self.dimensions_for(image_count)
|
20
|
+
case image_count
|
21
|
+
when 1 then [200, 200]
|
22
|
+
when 2 then [400, 200]
|
23
|
+
when 3 then [600, 200]
|
24
|
+
when 4 then [400, 400]
|
25
|
+
when 5..6 then [600, 400]
|
26
|
+
else [600, 600]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Piccle
|
2
|
+
# Renders a bunch of pages, based on the hash data loaded by the given parser.
|
3
|
+
class Renderer
|
4
|
+
def initialize(parser)
|
5
|
+
@parser = parser
|
6
|
+
@extractor = Piccle::Extractor.new(parser)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Given an array that contains a path (not including a :photos key), get that photo
|
10
|
+
# data and render an index page using the :photos data found at that location.
|
11
|
+
#
|
12
|
+
# For instance, if selector was ["by-date", "2015"] you'd get an index page of photos
|
13
|
+
# for 2015 based on the data held by the parser.
|
14
|
+
def render_index(selector)
|
15
|
+
Piccle::TemplateHelpers.render("index", render_index_template_vars(selector))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Renders the "main" index – the front page of our site.
|
19
|
+
def render_main_index
|
20
|
+
Piccle::TemplateHelpers.render("index", render_main_index_template_vars)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Renders an Atom feed of the given subsection.
|
24
|
+
def render_feed(selector = [])
|
25
|
+
photos = @parser.subsection_photos(selector).sort_by { |k, p| p[:created_at] }.reverse
|
26
|
+
escaped_selector = selector.map { |s| CGI::escape(s) }
|
27
|
+
|
28
|
+
template_vars = {
|
29
|
+
photos: photos,
|
30
|
+
joined_selector: "/#{escaped_selector.join("/")}/",
|
31
|
+
feed_update_time: photos.map { |k, v| v[:created_at] }.max,
|
32
|
+
selector: selector,
|
33
|
+
site_metadata: site_metadata
|
34
|
+
}
|
35
|
+
|
36
|
+
Piccle::TemplateHelpers.render_rss("feed", template_vars)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Render a page for a specific photo.
|
40
|
+
def render_photo(hash, selector=[])
|
41
|
+
Piccle::TemplateHelpers.render("show", render_photo_template_vars(hash, selector))
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
# Returns all the data we pass into the main index to render.
|
47
|
+
def render_main_index_template_vars
|
48
|
+
template_vars = {
|
49
|
+
photos: @parser.data[:photos],
|
50
|
+
event_starts: @parser.data[:event_starts],
|
51
|
+
event_ends: @parser.data[:event_ends],
|
52
|
+
nav_items: @extractor.navigation,
|
53
|
+
site_metadata: site_metadata
|
54
|
+
}
|
55
|
+
|
56
|
+
if Piccle.config.open_graph?
|
57
|
+
width, height = Piccle::QuiltGenerator.dimensions_for(@parser.data[:photos].length)
|
58
|
+
template_vars[:open_graph] = open_graph_for(title: site_title(),
|
59
|
+
description: "A gallery of photos by #{Piccle.config.author_name}",
|
60
|
+
image_url: "#{Piccle.config.home_url}quilt.jpg",
|
61
|
+
image_alt: "A quilt of the most recent images in this gallery.",
|
62
|
+
width: width,
|
63
|
+
height: height,
|
64
|
+
page_url: Piccle.config.home_url)
|
65
|
+
end
|
66
|
+
template_vars
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns all the data we pass into a template for rendering an index page, as a hash.
|
70
|
+
def render_index_template_vars(selector)
|
71
|
+
breadcrumbs = @extractor.breadcrumbs_for(selector)
|
72
|
+
selector_path = "#{selector.join('/')}/"
|
73
|
+
template_vars = {
|
74
|
+
photos: @parser.subsection_photos(selector),
|
75
|
+
event_starts: [],
|
76
|
+
event_ends: [],
|
77
|
+
nav_items: @extractor.navigation,
|
78
|
+
selector: selector,
|
79
|
+
selector_path: selector_path,
|
80
|
+
breadcrumbs: breadcrumbs,
|
81
|
+
site_url: Piccle.config.home_url,
|
82
|
+
include_prefix: Piccle::TemplateHelpers.include_prefix(selector),
|
83
|
+
site_metadata: site_metadata
|
84
|
+
}
|
85
|
+
|
86
|
+
if Piccle.config.open_graph?
|
87
|
+
width, height = Piccle::QuiltGenerator.dimensions_for(@parser.subsection_photo_hashes(selector).length)
|
88
|
+
template_vars[:open_graph] = open_graph_for(title: site_title(breadcrumbs),
|
89
|
+
description: "A gallery of photos by #{Piccle.config.author_name}",
|
90
|
+
image_url: "#{Piccle.config.home_url}#{selector_path}quilt.jpg",
|
91
|
+
image_alt: "A quilt of the most recent images in this gallery.",
|
92
|
+
width: width,
|
93
|
+
height: height,
|
94
|
+
page_url: "#{Piccle.config.home_url}#{selector_path}")
|
95
|
+
end
|
96
|
+
|
97
|
+
template_vars
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns all the template vars we use to render a photo page.
|
101
|
+
def render_photo_template_vars(hash, selector)
|
102
|
+
photo_data = @parser.data[:photos][hash]
|
103
|
+
substreams = [@parser.substream_for(hash)] + @parser.links_for(hash).map { |selector| @parser.interesting_substream_for(hash, selector) }.compact
|
104
|
+
|
105
|
+
template_vars = {
|
106
|
+
photo: photo_data,
|
107
|
+
selector: selector,
|
108
|
+
selector_path: selector.any? ? "#{selector.join('/')}/" : "",
|
109
|
+
breadcrumbs: @extractor.breadcrumbs_for(selector),
|
110
|
+
substreams: substreams.select { |stream| stream[:photos].length > 1 },
|
111
|
+
camera_link: @extractor.camera_link(hash),
|
112
|
+
keywords: @extractor.keywords(hash),
|
113
|
+
day_link: @extractor.day_link(hash),
|
114
|
+
month_link: @extractor.month_link(hash),
|
115
|
+
year_link: @extractor.year_link(hash),
|
116
|
+
city_link: @extractor.city_link(hash),
|
117
|
+
state_link: @extractor.state_link(hash),
|
118
|
+
country_link: @extractor.country_link(hash),
|
119
|
+
prev_link: @extractor.prev_link(hash, selector),
|
120
|
+
next_link: @extractor.next_link(hash, selector),
|
121
|
+
include_prefix: Piccle::TemplateHelpers.include_prefix(selector),
|
122
|
+
canonical: "photos/#{hash}.html", # TODO: Other paths live in piccle.rake. Why's this one here?
|
123
|
+
site_metadata: site_metadata
|
124
|
+
}
|
125
|
+
|
126
|
+
photo_title = photo_data[:title] || ""
|
127
|
+
photo_title = "Photo" if photo_title.empty?
|
128
|
+
template_vars[:breadcrumbs] << { friendly_name: photo_title } if selector.any?
|
129
|
+
|
130
|
+
if Piccle.config.open_graph?
|
131
|
+
template_vars[:open_graph] = open_graph_for(title: photo_data[:title] || "A photo by #{Piccle.config.author_name}",
|
132
|
+
description: photo_data[:description],
|
133
|
+
image_url: "#{Piccle.config.home_url}images/photos/#{hash}.#{photo_data[:file_name]}",
|
134
|
+
width: photo_data[:width],
|
135
|
+
height: photo_data[:height],
|
136
|
+
page_url: "#{Piccle.config.home_url}/#{hash}.html")
|
137
|
+
end
|
138
|
+
|
139
|
+
template_vars
|
140
|
+
end
|
141
|
+
|
142
|
+
# Gets information about our site, used on pretty much every page.
|
143
|
+
def site_metadata
|
144
|
+
unless @cached_site_metadata
|
145
|
+
min_year = Piccle::Photo.earliest_photo_year
|
146
|
+
max_year = Piccle::Photo.latest_photo_year
|
147
|
+
copyright_year = if min_year == max_year
|
148
|
+
max_year
|
149
|
+
else
|
150
|
+
"#{min_year} – #{max_year}"
|
151
|
+
end
|
152
|
+
|
153
|
+
@cached_site_metadata = { author_name: Piccle.config.author_name, copyright_year: copyright_year }
|
154
|
+
end
|
155
|
+
@cached_site_metadata
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns a hash of open graph data based on the parameters passed in.
|
159
|
+
def open_graph_for(title: nil, description: nil, image_url: nil, image_alt: nil, width: nil, height: nil, page_url: nil)
|
160
|
+
open_graph = { title: title, url: page_url, image: { width: width, height: height, url: image_url } }
|
161
|
+
open_graph[:image][:image_alt] = image_alt if image_alt
|
162
|
+
open_graph[:description] = description if description
|
163
|
+
open_graph
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns a human-readable title for this site.
|
167
|
+
def site_title(breadcrumbs = [])
|
168
|
+
title = "Photography by #{Piccle.config.author_name}"
|
169
|
+
if breadcrumbs.any?
|
170
|
+
title += " - " + breadcrumbs.map { |b| b[:friendly_name] }.join(" - ")
|
171
|
+
end
|
172
|
+
title
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Streams are a self-contained way of faceting data for a photo.
|
4
|
+
|
5
|
+
class Piccle::Streams::BaseStream
|
6
|
+
#
|
7
|
+
def namespace
|
8
|
+
"by-foo"
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns a hash that contains data to merge for the given photo.
|
12
|
+
def data_for(photo)
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
|
16
|
+
# def metadata_for(photo)
|
17
|
+
# [{}]
|
18
|
+
# end
|
19
|
+
|
20
|
+
# Reorder the data within this stream.
|
21
|
+
#
|
22
|
+
# The parser calls #order on every stream once it's loaded all the photos. You can reorder your data as you see fit.
|
23
|
+
# For instance, most general streams will want to order their subphotos by date - but if you're a keyword stream,
|
24
|
+
# you might also put the most popular keywords first.
|
25
|
+
#
|
26
|
+
# The default implementation organises facets with the most popular items first, and reorders the photos by date.
|
27
|
+
#
|
28
|
+
# Each stream is expected to only meddle within its own namespace, but this is not enforced.
|
29
|
+
def order(data)
|
30
|
+
if data.key?(namespace)
|
31
|
+
data[namespace] = data[namespace].sort_by(&length_sort_proc(data)).reverse.to_h
|
32
|
+
data[namespace].each do |k, v|
|
33
|
+
data[namespace][k][:photos] = data[namespace][k][:photos].sort_by(&date_sort_proc(data)).reverse if k.is_a?(String)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
data
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# A sort proc designed for hashes. Sorts all string keys in order of how many photos they contain.
|
43
|
+
def length_sort_proc(data)
|
44
|
+
Proc.new { |k, v| k.is_a?(String) ? data.dig(namespace, k, :photos)&.length : 0 }
|
45
|
+
end
|
46
|
+
|
47
|
+
# A date sort designed for arrays. Sorts all photo hashes in order of the date they were taken.
|
48
|
+
def date_sort_proc(data)
|
49
|
+
Proc.new { |hash| data.dig(:photos, hash, :taken_at) || Time.new(1970, 1, 1) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Converts a sentence into something suitable for use in a URL slug.
|
53
|
+
def slugify(name)
|
54
|
+
name.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-+/, '').gsub(/-+$/, '')
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Browse photos by camera.
|
4
|
+
class Piccle::Streams::CameraStream < Piccle::Streams::BaseStream
|
5
|
+
def namespace
|
6
|
+
"by-camera"
|
7
|
+
end
|
8
|
+
|
9
|
+
def data_for(photo)
|
10
|
+
{
|
11
|
+
namespace => {
|
12
|
+
:friendly_name => "By Camera",
|
13
|
+
:interesting => false,
|
14
|
+
slugify(camera_name(photo)) => {
|
15
|
+
friendly_name: camera_name(photo),
|
16
|
+
photos: [photo.md5]
|
17
|
+
},
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def metadata_for(photo)
|
23
|
+
[{
|
24
|
+
friendly_name: camera_name(photo),
|
25
|
+
type: :camera,
|
26
|
+
selector: [namespace, slugify(camera_name(photo))]
|
27
|
+
}]
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def camera_name(photo)
|
33
|
+
photo.camera_name || "unknown"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Enables browsing photos by date.
|
4
|
+
|
5
|
+
class Piccle::Streams::DateStream < Piccle::Streams::BaseStream
|
6
|
+
def namespace
|
7
|
+
"by-date"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Standard method called by the parser object. This should return a hash that contains sub-categories (optionally)
|
11
|
+
# and a list of :photos for each tier.
|
12
|
+
def data_for(photo)
|
13
|
+
year, month, day = photo.taken_at&.year, photo.taken_at&.month, photo.taken_at&.day
|
14
|
+
if year && month && day
|
15
|
+
{ namespace => {
|
16
|
+
:friendly_name => "By Date",
|
17
|
+
:interesting => false,
|
18
|
+
year.to_s => {
|
19
|
+
:friendly_name => "#{year}",
|
20
|
+
:interesting => false,
|
21
|
+
month.to_s => {
|
22
|
+
:friendly_name => "#{Date::MONTHNAMES[month]}",
|
23
|
+
:interesting => false,
|
24
|
+
day.to_s => {
|
25
|
+
:friendly_name => "#{day}#{ordinal_for(day)}",
|
26
|
+
:interesting => false,
|
27
|
+
:photos => [photo.md5]
|
28
|
+
},
|
29
|
+
:photos => [photo.md5]
|
30
|
+
},
|
31
|
+
photos: [photo.md5]
|
32
|
+
}
|
33
|
+
}}
|
34
|
+
else
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def metadata_for(photo)
|
40
|
+
year, month, day = photo.taken_at&.year, photo.taken_at&.month, photo.taken_at&.day
|
41
|
+
if year && month && day
|
42
|
+
[{ friendly_name: "#{day}#{ordinal_for(day)}",
|
43
|
+
type: :date_day,
|
44
|
+
selector: [namespace, year, month, day]
|
45
|
+
}, {
|
46
|
+
friendly_name: "#{Date::MONTHNAMES[month]}",
|
47
|
+
type: :date_month,
|
48
|
+
selector: [namespace, year, month]
|
49
|
+
}, {
|
50
|
+
friendly_name: year.to_s,
|
51
|
+
type: :date_year,
|
52
|
+
selector: [namespace, year]
|
53
|
+
}]
|
54
|
+
else
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Standard method called by the parser object. Gives this stream an option to re-order its data. The stream is on
|
60
|
+
# its honour to only meddle within its own namespace.
|
61
|
+
def order(data)
|
62
|
+
sort_proc = Proc.new { |k, v| k.is_a?(String) ? k : "" }
|
63
|
+
|
64
|
+
data[namespace] = data[namespace].sort_by(&sort_proc).to_h # Sort years
|
65
|
+
|
66
|
+
data[namespace].each do |year_k, v|
|
67
|
+
# Sort photos in each year, and then the month keys
|
68
|
+
if year_k.is_a?(String)
|
69
|
+
if data[namespace][year_k].key?(:photos)
|
70
|
+
data[namespace][year_k][:photos] = data[namespace][year_k][:photos].sort_by(&date_sort_proc(data)).reverse
|
71
|
+
end
|
72
|
+
data[namespace][year_k] = data[namespace][year_k].sort_by(&sort_proc).to_h
|
73
|
+
|
74
|
+
data[namespace][year_k].each do |month_k, v|
|
75
|
+
# Sort photos in each month, and then the days. TODO.
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
data
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def ordinal_for(num)
|
85
|
+
if num % 10 == 1 && num != 11
|
86
|
+
"st"
|
87
|
+
elsif num % 10 == 2 && num != 12
|
88
|
+
"nd"
|
89
|
+
elsif num % 10 == 3 && num != 13
|
90
|
+
"rd"
|
91
|
+
else
|
92
|
+
"th"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# A special-case stream that handles named "events". You can define details in events.yaml - with things like a name,
|
4
|
+
# dates, and whether it should be collapsed or not on the front page.
|
5
|
+
|
6
|
+
class Piccle::Streams::EventStream < Piccle::Streams::BaseStream
|
7
|
+
attr_accessor :events
|
8
|
+
|
9
|
+
def namespace
|
10
|
+
"by-event"
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@events = if File.exist?(Piccle.config.events_file)
|
15
|
+
YAML.load_file(Piccle.config.events_file).map do |event| # Convert keys to symbols; bring dates to life.
|
16
|
+
event = event.map { |k, v| [k.to_sym, v] }.to_h
|
17
|
+
if event[:from].is_a? Date
|
18
|
+
event[:from] = DateTime.new(event[:from].year, event[:from].month, event[:from].day, 0, 0, 0)
|
19
|
+
end
|
20
|
+
if event[:to].is_a? Date
|
21
|
+
event[:to] = DateTime.new(event[:to].year, event[:to].month, event[:to].day, 23, 59, 59)
|
22
|
+
end
|
23
|
+
event
|
24
|
+
end
|
25
|
+
else
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def data_for(photo)
|
31
|
+
if photo.taken_at
|
32
|
+
relevant_events = @events.select { |ev| photo.taken_at.to_datetime >= ev[:from] && photo.taken_at.to_datetime <= ev[:to] }
|
33
|
+
result = { namespace => { friendly_name: "By Event", interesting: true }}
|
34
|
+
relevant_events.each do |ev|
|
35
|
+
result[namespace][slugify(ev[:name])] = { friendly_name: ev[:name], interesting: true, photos: [photo.md5] }
|
36
|
+
end
|
37
|
+
|
38
|
+
result
|
39
|
+
else
|
40
|
+
{}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sorts most recent events first; then organises photos by date. TODO
|
45
|
+
def order(data)
|
46
|
+
super(data)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Given an event name, get a selector hash for this event.
|
50
|
+
def selector_for(name)
|
51
|
+
[namespace, slugify(name)]
|
52
|
+
end
|
53
|
+
|
54
|
+
# "Sentinels" are data hashes that mark the start and end of an event. We use them to render tiles in the overall
|
55
|
+
# index page, if we have photos for the declared event. Each event has an event_start and an event_end marker.
|
56
|
+
#
|
57
|
+
# Returns an array of 2 items: [event_starts, event_ends]
|
58
|
+
def sentinels_for(data)
|
59
|
+
event_starts = {}
|
60
|
+
event_ends = {}
|
61
|
+
@events.each do |event|
|
62
|
+
slug = slugify(event[:name])
|
63
|
+
if data.dig(namespace, slug, :photos)&.any?
|
64
|
+
most_recent_hash = data[namespace][slug][:photos].first
|
65
|
+
oldest_hash = data[namespace][slug][:photos].last # Event starts are the furthest back in time!
|
66
|
+
event_starts[oldest_hash] = { name: event[:name], selector: selector_for(event[:name]) }
|
67
|
+
event_ends[most_recent_hash] = { name: event[:name], selector: selector_for(event[:name]) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
[event_starts, event_ends]
|
72
|
+
end
|
73
|
+
end
|