piccle 0.1.0.rc1 → 0.1.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{agpl-3.0.md → LICENSE.md} +0 -0
- data/NOTES.md +8 -17
- data/README.md +66 -18
- data/assets/css/default.css +87 -19
- data/bin/piccle +31 -18
- data/db/migrations/006_create_people_and_join_table.rb +14 -0
- data/db/migrations/007_add_indexes.rb +14 -0
- data/lib/piccle.rb +3 -2
- data/lib/piccle/extractor.rb +14 -5
- data/lib/piccle/js_renderer.rb +4 -2
- data/lib/piccle/models/person.rb +6 -0
- data/lib/piccle/models/photo.rb +28 -4
- data/lib/piccle/parser.rb +7 -1
- data/lib/piccle/renderer.rb +61 -10
- data/lib/piccle/streams/base_stream.rb +7 -3
- data/lib/piccle/streams/date_stream.rb +1 -1
- data/lib/piccle/streams/event_stream.rb +34 -11
- data/lib/piccle/streams/keyword_stream.rb +2 -1
- data/lib/piccle/streams/person_stream.rb +16 -0
- data/lib/piccle/version.rb +1 -1
- data/templates/_header.handlebars.slim +8 -4
- data/templates/index.html.handlebars.slim +55 -13
- data/templates/show.html.handlebars.slim +59 -57
- metadata +7 -4
- data/.travis.yml +0 -5
@@ -0,0 +1,14 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table(:people) do
|
4
|
+
primary_key :id
|
5
|
+
String :name, null: false, unique: true
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table(:people_photos) do
|
9
|
+
Integer :person_id, null: false
|
10
|
+
Integer :photo_id, null: false
|
11
|
+
primary_key [:person_id, :photo_id], name: :people_photos_pk
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
alter_table(:photos) do
|
4
|
+
add_index :md5, unique: true
|
5
|
+
add_index :file_name
|
6
|
+
add_index :path
|
7
|
+
add_index [:path, :file_name], unique: true
|
8
|
+
end
|
9
|
+
|
10
|
+
# Keywords and people are case-insensitive, enforce that in the DB
|
11
|
+
alter_table(:keywords) { add_index Sequel.function(:lower, :name), unique: true }
|
12
|
+
alter_table(:people) { add_index Sequel.function(:lower, :name), unique: true }
|
13
|
+
end
|
14
|
+
end
|
data/lib/piccle.rb
CHANGED
@@ -13,6 +13,7 @@ require "piccle/streams/date_stream"
|
|
13
13
|
require "piccle/streams/event_stream"
|
14
14
|
require "piccle/streams/keyword_stream"
|
15
15
|
require "piccle/streams/location_stream"
|
16
|
+
require "piccle/streams/person_stream"
|
16
17
|
require "piccle/template_helpers"
|
17
18
|
require "piccle/version"
|
18
19
|
|
@@ -38,9 +39,9 @@ module Piccle
|
|
38
39
|
def Piccle.const_missing(name)
|
39
40
|
Sequel::Model.db = Piccle.config.db
|
40
41
|
|
41
|
-
if %i[
|
42
|
+
if %i[Keyword Location Person Photo].include?(name)
|
42
43
|
Dir[Piccle.config.gem_root_join("lib", "piccle", "models", "*.rb")].each { |f| require f.delete_prefix("lib/").delete_suffix(".rb") }
|
43
|
-
models = [Piccle::
|
44
|
+
models = [Piccle::Keyword, Piccle::Location, Piccle::Person, Piccle::Photo]
|
44
45
|
models.each(&:finalize_associations)
|
45
46
|
models.each(&:freeze)
|
46
47
|
end
|
data/lib/piccle/extractor.rb
CHANGED
@@ -51,7 +51,7 @@ module Piccle
|
|
51
51
|
|
52
52
|
index = hashes.index(photo_hash)
|
53
53
|
if index && index > 0
|
54
|
-
"#{
|
54
|
+
"#{hashes[index-1]}.html"
|
55
55
|
else
|
56
56
|
nil
|
57
57
|
end
|
@@ -64,22 +64,31 @@ module Piccle
|
|
64
64
|
|
65
65
|
index = hashes.index(photo_hash)
|
66
66
|
if index && index < (hashes.length - 1)
|
67
|
-
"#{
|
67
|
+
"#{hashes[index + 1]}.html"
|
68
68
|
else
|
69
69
|
nil
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
# Gets the photos to show on the main index page. This is everything, except for photos within a collapsed event.
|
74
|
+
# We keep the first photo, but this is typically rendererd as a quilt tile rather than the photo itself.
|
75
|
+
def main_index_photos
|
76
|
+
all_photos = @parser.data[:photos]
|
77
|
+
collapsed_events = @parser.data[:events].select { |ev| ev[:collapsed] }
|
78
|
+
removable_hashes = collapsed_events.flat_map { |ev| @parser.subsection_photo_hashes(ev[:selector]).drop(1) }
|
79
|
+
all_photos.reject { |k, _v| removable_hashes.include?(k) }
|
80
|
+
end
|
81
|
+
|
73
82
|
# Gets a (currently top-level only) navigation structure. All entries have at least one photo.
|
74
83
|
def navigation
|
75
84
|
navigation_entries = @parser.faceted_data.map do |k, v|
|
76
85
|
{ friendly_name: v[:friendly_name],
|
77
|
-
|
86
|
+
min_for_nav: v[:min_for_nav],
|
87
|
+
entries: entries_for(v, k),
|
78
88
|
}
|
79
89
|
end
|
80
|
-
# A hack, to only show keywords that have more than one image.
|
81
90
|
navigation_entries.each do |nav_entry|
|
82
|
-
nav_entry[:entries].keep_if { |entry| entry[:photo_count]
|
91
|
+
nav_entry[:entries].keep_if { |entry| entry[:photo_count] >= nav_entry[:min_for_nav] } if nav_entry[:min_for_nav]
|
83
92
|
end
|
84
93
|
end
|
85
94
|
|
data/lib/piccle/js_renderer.rb
CHANGED
@@ -9,11 +9,13 @@ module Piccle
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def render_main_index
|
12
|
-
|
12
|
+
template_vars = paginate(render_main_index_template_vars)
|
13
|
+
template_vars.map { |page_template_vars| call_nodejs("index", page_template_vars) }
|
13
14
|
end
|
14
15
|
|
15
16
|
def render_index(selector)
|
16
|
-
|
17
|
+
template_vars = paginate(render_index_template_vars(selector))
|
18
|
+
template_vars.map { |page_template_vars| call_nodejs("index", page_template_vars) }
|
17
19
|
end
|
18
20
|
|
19
21
|
def render_photo(hash, selector = [])
|
data/lib/piccle/models/photo.rb
CHANGED
@@ -8,6 +8,7 @@ require 'json'
|
|
8
8
|
# Represents an image in the system. Reading info from an image? Inferring something based on the data? Put it here.
|
9
9
|
class Piccle::Photo < Sequel::Model
|
10
10
|
many_to_many :keywords
|
11
|
+
many_to_many :people
|
11
12
|
attr_accessor :changed_hash # Has this file been modified?
|
12
13
|
attr_accessor :freshly_created # Have we just generated this file?
|
13
14
|
|
@@ -29,7 +30,10 @@ class Piccle::Photo < Sequel::Model
|
|
29
30
|
photo.freshly_created = freshly_created
|
30
31
|
|
31
32
|
# Pull out keywords for this file, if it's new or changed.
|
32
|
-
|
33
|
+
if freshly_created || photo.changed_hash?
|
34
|
+
photo.sync_keywords
|
35
|
+
photo.sync_people
|
36
|
+
end
|
33
37
|
|
34
38
|
photo
|
35
39
|
end
|
@@ -183,16 +187,36 @@ class Piccle::Photo < Sequel::Model
|
|
183
187
|
end
|
184
188
|
|
185
189
|
# Read the keywords from the photo file, and ensure they're included in the DB.
|
186
|
-
|
187
|
-
def generate_keywords
|
190
|
+
def sync_keywords
|
188
191
|
exif_info = EXIFR::JPEG.new(original_photo_path)
|
189
192
|
xmp = XMP.parse(exif_info)
|
190
193
|
|
191
194
|
if xmp && xmp.namespaces && xmp.namespaces.include?("dc") && xmp.dc.attributes.include?("subject")
|
192
|
-
|
195
|
+
# Add new keywords
|
196
|
+
downcased_keywords = xmp.dc.subject.map(&:downcase)
|
197
|
+
downcased_keywords.each do |keyword|
|
193
198
|
keyword = Piccle::Keyword.find_or_create(name: keyword)
|
194
199
|
add_keyword(keyword) unless keywords.include?(keyword)
|
195
200
|
end
|
201
|
+
|
202
|
+
# Remove old keywords
|
203
|
+
keywords.each { |keyword| remove_keyword(keyword) unless downcased_keywords.include?(keyword.name.downcase) }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def sync_people
|
208
|
+
exif_info = EXIFR::JPEG.new(original_photo_path)
|
209
|
+
xmp = XMP.parse(exif_info)
|
210
|
+
|
211
|
+
if xmp && xmp.namespaces && xmp.namespaces.include?("Iptc4xmpExt") && xmp.Iptc4xmpExt.attributes.include?("PersonInImage")
|
212
|
+
people_names = xmp.Iptc4xmpExt.PersonInImage
|
213
|
+
people_names.each do |name|
|
214
|
+
person = Piccle::Person.find(Sequel.ilike(:name, name)) || Piccle::Person.create(name: name)
|
215
|
+
add_person(person) unless people.include?(person)
|
216
|
+
end
|
217
|
+
|
218
|
+
downcased_names = people_names.map(&:downcase)
|
219
|
+
people.each { |person| remove_person(person) unless downcased_names.include?(person.name.downcase) }
|
196
220
|
end
|
197
221
|
end
|
198
222
|
|
data/lib/piccle/parser.rb
CHANGED
@@ -120,7 +120,8 @@ module Piccle
|
|
120
120
|
|
121
121
|
# Get photo hashes in a given subsection, given a diggable path.
|
122
122
|
def subsection_photo_hashes(subsection_path)
|
123
|
-
@data.dig(*subsection_path)
|
123
|
+
subsection = @data.dig(*subsection_path)
|
124
|
+
subsection ? subsection.fetch(:photos, []) : []
|
124
125
|
end
|
125
126
|
|
126
127
|
# Gets the actual photo objects for a given subsection.
|
@@ -132,6 +133,11 @@ module Piccle
|
|
132
133
|
end
|
133
134
|
end
|
134
135
|
|
136
|
+
# Determines whether the given subsection is marked as "collapsed". This is currently specific to event streams.
|
137
|
+
def subsection_collapsed?(subsection_path)
|
138
|
+
@data.dig(*subsection_path).fetch(:collapsed, false)
|
139
|
+
end
|
140
|
+
|
135
141
|
# Given an MD5 hash, returns an array of arrays. Each array is a set of strings that, combined with the MD5, gives a link to the photo.
|
136
142
|
# So for instance, with a date stream parser, if a photo was taken on 2016-04-19 you'll get back:
|
137
143
|
# [["by-date", "2016"], ["by-date", "2016", "4"], ["by-date", "2016", "4", "19"]]
|
data/lib/piccle/renderer.rb
CHANGED
@@ -12,12 +12,14 @@ module Piccle
|
|
12
12
|
# For instance, if selector was ["by-date", "2015"] you'd get an index page of photos
|
13
13
|
# for 2015 based on the data held by the parser.
|
14
14
|
def render_index(selector)
|
15
|
-
|
15
|
+
template_vars = paginate(render_index_template_vars(selector))
|
16
|
+
template_vars.map { |page_template_vars| Piccle::TemplateHelpers.render("index", page_template_vars) }
|
16
17
|
end
|
17
18
|
|
18
19
|
# Renders the "main" index – the front page of our site.
|
19
20
|
def render_main_index
|
20
|
-
|
21
|
+
template_vars = paginate(render_main_index_template_vars)
|
22
|
+
template_vars.map { |page_template_vars| Piccle::TemplateHelpers.render("index", page_template_vars) }
|
21
23
|
end
|
22
24
|
|
23
25
|
# Renders an Atom feed of the given subsection.
|
@@ -41,22 +43,30 @@ module Piccle
|
|
41
43
|
Piccle::TemplateHelpers.render("show", render_photo_template_vars(hash, selector))
|
42
44
|
end
|
43
45
|
|
46
|
+
# What should we call this index page? The first page is "index" (ie. index.html), later ones have a number
|
47
|
+
# (eg. "index-02"). NB! Our indexes are 0 based, but our pages are 1 based. (0 → index, 1 → index-02, 2 → index-03).
|
48
|
+
def index_page_name_for(index)
|
49
|
+
index.zero? ? "index" : "index-#{sprintf("%02d", index + 1)}"
|
50
|
+
end
|
51
|
+
|
44
52
|
protected
|
45
53
|
|
46
54
|
# Returns all the data we pass into the main index to render.
|
47
55
|
def render_main_index_template_vars
|
56
|
+
page_description = "A gallery of photos by #{Piccle.config.author_name}"
|
48
57
|
template_vars = {
|
49
|
-
photos: @
|
58
|
+
photos: @extractor.main_index_photos,
|
50
59
|
event_starts: @parser.data[:event_starts],
|
51
60
|
event_ends: @parser.data[:event_ends],
|
52
61
|
nav_items: @extractor.navigation,
|
53
|
-
site_metadata: site_metadata
|
62
|
+
site_metadata: site_metadata,
|
63
|
+
page_description: page_description
|
54
64
|
}
|
55
65
|
|
56
66
|
if Piccle.config.open_graph?
|
57
67
|
width, height = Piccle::QuiltGenerator.dimensions_for(@parser.data[:photos].length)
|
58
68
|
template_vars[:open_graph] = open_graph_for(title: site_title(),
|
59
|
-
description:
|
69
|
+
description: page_description,
|
60
70
|
image_url: "#{Piccle.config.home_url}quilt.jpg",
|
61
71
|
image_alt: "A quilt of the most recent images in this gallery.",
|
62
72
|
width: width,
|
@@ -70,6 +80,7 @@ module Piccle
|
|
70
80
|
def render_index_template_vars(selector)
|
71
81
|
breadcrumbs = @extractor.breadcrumbs_for(selector)
|
72
82
|
selector_path = "#{selector.join('/')}/"
|
83
|
+
page_description = "A gallery of photos by #{Piccle.config.author_name}"
|
73
84
|
template_vars = {
|
74
85
|
photos: @parser.subsection_photos(selector),
|
75
86
|
event_starts: [],
|
@@ -80,13 +91,14 @@ module Piccle
|
|
80
91
|
breadcrumbs: breadcrumbs,
|
81
92
|
site_url: Piccle.config.home_url,
|
82
93
|
include_prefix: Piccle::TemplateHelpers.include_prefix(selector),
|
83
|
-
site_metadata: site_metadata
|
94
|
+
site_metadata: site_metadata,
|
95
|
+
page_description: page_description
|
84
96
|
}
|
85
97
|
|
86
98
|
if Piccle.config.open_graph?
|
87
99
|
width, height = Piccle::QuiltGenerator.dimensions_for(@parser.subsection_photo_hashes(selector).length)
|
88
100
|
template_vars[:open_graph] = open_graph_for(title: site_title(breadcrumbs),
|
89
|
-
description:
|
101
|
+
description: page_description,
|
90
102
|
image_url: "#{Piccle.config.home_url}#{selector_path}quilt.jpg",
|
91
103
|
image_alt: "A quilt of the most recent images in this gallery.",
|
92
104
|
width: width,
|
@@ -97,10 +109,48 @@ module Piccle
|
|
97
109
|
template_vars
|
98
110
|
end
|
99
111
|
|
112
|
+
# Given a set of template variables, paginate them. Specifically:
|
113
|
+
# - Take the :photos key, and break it into chunks
|
114
|
+
# - Add a :pagination key with details of all the pages
|
115
|
+
# - Returns an array of template vars.
|
116
|
+
def paginate(template_vars)
|
117
|
+
slices = template_vars[:photos].each_slice(100)
|
118
|
+
if slices.count > 1
|
119
|
+
pages = (0...slices.count).map { |i| { label: i + 1, page_name: index_page_name_for(i) } }
|
120
|
+
|
121
|
+
slices.map.with_index do |slice, i|
|
122
|
+
pages_with_current = pages.map.with_index { |page, index| page.merge(is_current: (i == index)) }
|
123
|
+
is_first = i.zero?
|
124
|
+
is_last = i == slices.count - 1
|
125
|
+
|
126
|
+
new_vars = { photos: slice.to_h }
|
127
|
+
new_vars[:prev_link] = "#{index_page_name_for(i-1)}.html" unless is_first
|
128
|
+
new_vars[:next_link] = "#{index_page_name_for(i+1)}.html" unless is_last
|
129
|
+
|
130
|
+
pagination = { pagination: { page_name: index_page_name_for(i),
|
131
|
+
pages: pages_with_current,
|
132
|
+
is_first_page: is_first,
|
133
|
+
is_last_page: is_last,
|
134
|
+
previous_page_name: index_page_name_for(i-1),
|
135
|
+
next_page_name: index_page_name_for(i+1),
|
136
|
+
total_pages: slices.count }}
|
137
|
+
|
138
|
+
template_vars.merge(new_vars, pagination)
|
139
|
+
end
|
140
|
+
else # Just the one page, no point in adding any pagination.
|
141
|
+
[template_vars]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
100
145
|
# Returns all the template vars we use to render a photo page.
|
101
146
|
def render_photo_template_vars(hash, selector)
|
102
147
|
photo_data = @parser.data[:photos][hash]
|
103
148
|
substreams = [@parser.substream_for(hash)] + @parser.links_for(hash).map { |selector| @parser.interesting_substream_for(hash, selector) }.compact
|
149
|
+
page_description = if photo_data[:description] && photo_data[:description].strip.length > 0
|
150
|
+
photo_data[:description]
|
151
|
+
else
|
152
|
+
"A photo by #{Piccle.config.author_name}"
|
153
|
+
end
|
104
154
|
|
105
155
|
template_vars = {
|
106
156
|
photo: photo_data,
|
@@ -119,8 +169,9 @@ module Piccle
|
|
119
169
|
prev_link: @extractor.prev_link(hash, selector),
|
120
170
|
next_link: @extractor.next_link(hash, selector),
|
121
171
|
include_prefix: Piccle::TemplateHelpers.include_prefix(selector),
|
122
|
-
canonical: "
|
123
|
-
site_metadata: site_metadata
|
172
|
+
canonical: "#{hash}.html",
|
173
|
+
site_metadata: site_metadata,
|
174
|
+
page_description: page_description
|
124
175
|
}
|
125
176
|
|
126
177
|
photo_title = photo_data[:title] || ""
|
@@ -129,7 +180,7 @@ module Piccle
|
|
129
180
|
|
130
181
|
if Piccle.config.open_graph?
|
131
182
|
template_vars[:open_graph] = open_graph_for(title: photo_data[:title] || "A photo by #{Piccle.config.author_name}",
|
132
|
-
description:
|
183
|
+
description: page_description,
|
133
184
|
image_url: "#{Piccle.config.home_url}images/photos/#{hash}.#{photo_data[:file_name]}",
|
134
185
|
width: photo_data[:width],
|
135
186
|
height: photo_data[:height],
|
@@ -28,7 +28,7 @@ class Piccle::Streams::BaseStream
|
|
28
28
|
# Each stream is expected to only meddle within its own namespace, but this is not enforced.
|
29
29
|
def order(data)
|
30
30
|
if data.key?(namespace)
|
31
|
-
data[namespace] = data[namespace].sort_by(&length_sort_proc(data)).
|
31
|
+
data[namespace] = data[namespace].sort_by(&length_sort_proc(data)).to_h
|
32
32
|
data[namespace].each do |k, v|
|
33
33
|
data[namespace][k][:photos] = data[namespace][k][:photos].sort_by(&date_sort_proc(data)).reverse if k.is_a?(String)
|
34
34
|
end
|
@@ -39,9 +39,13 @@ class Piccle::Streams::BaseStream
|
|
39
39
|
|
40
40
|
protected
|
41
41
|
|
42
|
-
# A sort proc designed for hashes. Sorts all string keys in order of how many photos they contain.
|
42
|
+
# A sort proc designed for hashes. Sorts all string keys in order of how many photos they contain, then alphabetically.
|
43
43
|
def length_sort_proc(data)
|
44
|
-
Proc.new
|
44
|
+
Proc.new do |k, v|
|
45
|
+
length = k.is_a?(String) ? data.dig(namespace, k, :photos)&.length || 0 : 0
|
46
|
+
name = k.is_a?(String) ? k.downcase : ""
|
47
|
+
[length * -1, name]
|
48
|
+
end
|
45
49
|
end
|
46
50
|
|
47
51
|
# A date sort designed for arrays. Sorts all photo hashes in order of the date they were taken.
|
@@ -61,7 +61,7 @@ class Piccle::Streams::DateStream < Piccle::Streams::BaseStream
|
|
61
61
|
def order(data)
|
62
62
|
sort_proc = Proc.new { |k, v| k.is_a?(String) ? k : "" }
|
63
63
|
|
64
|
-
data[namespace] = data[namespace].sort_by(&sort_proc).to_h # Sort years
|
64
|
+
data[namespace] = data[namespace].sort_by(&sort_proc).reverse.to_h # Sort years
|
65
65
|
|
66
66
|
data[namespace].each do |year_k, v|
|
67
67
|
# Sort photos in each year, and then the month keys
|
@@ -14,13 +14,8 @@ class Piccle::Streams::EventStream < Piccle::Streams::BaseStream
|
|
14
14
|
@events = if File.exist?(Piccle.config.events_file)
|
15
15
|
YAML.load_file(Piccle.config.events_file).map do |event| # Convert keys to symbols; bring dates to life.
|
16
16
|
event = event.map { |k, v| [k.to_sym, v] }.to_h
|
17
|
-
|
18
|
-
|
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
|
17
|
+
event[:selector] = selector_for(event[:name])
|
18
|
+
transform_dates(event)
|
24
19
|
end
|
25
20
|
else
|
26
21
|
[]
|
@@ -32,7 +27,8 @@ class Piccle::Streams::EventStream < Piccle::Streams::BaseStream
|
|
32
27
|
relevant_events = @events.select { |ev| photo.taken_at.to_datetime >= ev[:from] && photo.taken_at.to_datetime <= ev[:to] }
|
33
28
|
result = { namespace => { friendly_name: "By Event", interesting: true }}
|
34
29
|
relevant_events.each do |ev|
|
35
|
-
result[namespace][slugify(ev[:name])] = { friendly_name: ev[:name], interesting: true, photos: [photo.md5]
|
30
|
+
result[namespace][slugify(ev[:name])] = { friendly_name: ev[:name], interesting: true, photos: [photo.md5],
|
31
|
+
collapsed: ev[:collapsed], sort_date: ev[:from] }
|
36
32
|
end
|
37
33
|
|
38
34
|
result
|
@@ -43,7 +39,14 @@ class Piccle::Streams::EventStream < Piccle::Streams::BaseStream
|
|
43
39
|
|
44
40
|
# Sorts most recent events first; then organises photos by date. TODO
|
45
41
|
def order(data)
|
46
|
-
|
42
|
+
if data.key?(namespace)
|
43
|
+
data[namespace] = data[namespace].sort_by { |k, v| k.is_a?(String) ? v[:sort_date] : DateTime.new(1826, 1, 1) }.reverse.to_h
|
44
|
+
data[namespace].each do |k, v|
|
45
|
+
data[namespace][k][:photos] = data[namespace][k][:photos].sort_by(&date_sort_proc(data)).reverse if k.is_a?(String)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
data
|
47
50
|
end
|
48
51
|
|
49
52
|
# Given an event name, get a selector hash for this event.
|
@@ -63,11 +66,31 @@ class Piccle::Streams::EventStream < Piccle::Streams::BaseStream
|
|
63
66
|
if data.dig(namespace, slug, :photos)&.any?
|
64
67
|
most_recent_hash = data[namespace][slug][:photos].first
|
65
68
|
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]) }
|
69
|
+
event_starts[oldest_hash] = { name: event[:name], selector: selector_for(event[:name]), collapsed: event[:collapsed] }
|
70
|
+
event_ends[most_recent_hash] = { name: event[:name], selector: selector_for(event[:name]), collapsed: event[:collapsed] }
|
68
71
|
end
|
69
72
|
end
|
70
73
|
|
71
74
|
[event_starts, event_ends]
|
72
75
|
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
# Given an event, munge the dates appropriately.
|
80
|
+
# - If we have an "at" date specified, make the from/to fields match the start and end of the day.
|
81
|
+
# - If we have "from"/"to" specified as dates, line them up with the start/end of the day.
|
82
|
+
def transform_dates(event)
|
83
|
+
if event[:at]
|
84
|
+
event[:from] = DateTime.new(event[:at].year, event[:at].month, event[:at].day, 0, 0, 0)
|
85
|
+
event[:to] = DateTime.new(event[:at].year, event[:at].month, event[:at].day, 23, 59, 59)
|
86
|
+
elsif event[:from] && event[:to]
|
87
|
+
if event[:from].is_a? Date
|
88
|
+
event[:from] = DateTime.new(event[:from].year, event[:from].month, event[:from].day, 0, 0, 0)
|
89
|
+
end
|
90
|
+
if event[:to].is_a? Date
|
91
|
+
event[:to] = DateTime.new(event[:to].year, event[:to].month, event[:to].day, 23, 59, 59)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
event
|
95
|
+
end
|
73
96
|
end
|