Skip to content

Commit 0fdef7a

Browse files
committedNov 19, 2022
add support for film details
this is because the Seattle listings don't have a good way to get a deeplink to film details. would be good to generalize how this is handled in the future but for now this is ok...
1 parent 6d2961a commit 0fdef7a

File tree

6 files changed

+155
-18
lines changed

6 files changed

+155
-18
lines changed
 

‎example/seattle.json

+21-3
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,41 @@
99
"driver": "elevent",
1010
"driverArgs": {
1111
"permalink": "SIFF",
12-
"venueID": "719"
12+
"venueIDs": [
13+
"721",
14+
"773",
15+
"774"
16+
]
17+
}
18+
},
19+
{
20+
"name": "SIFF Film Center",
21+
"driver": "elevent",
22+
"driverArgs": {
23+
"permalink": "SIFF",
24+
"venueIDs": [
25+
"720"
26+
]
1327
}
1428
},
1529
{
1630
"name": "SIFF Cinema Egyptian",
1731
"driver": "elevent",
1832
"driverArgs": {
1933
"permalink": "SIFF",
20-
"venueID": "721"
34+
"venueIDs": [
35+
"719"
36+
]
2137
}
2238
},
2339
{
2440
"name": "Central Cinema",
2541
"driver": "elevent",
2642
"driverArgs": {
2743
"permalink": "CentralCinema",
28-
"venueID": ""
44+
"venueIDs": [
45+
"1658"
46+
]
2947
}
3048
},
3149
{

‎internal/renderer/html.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,20 @@ type ShowtimeTemplateVars struct {
1717
ShowtimeISO string
1818
ShowtimeDisplay string
1919
Language string
20-
Search string
20+
SearchIndex string
21+
DetailsIndex string
2122
DeepLink string
2223
}
2324

25+
type DetailsTemplateVars struct {
26+
Film string
27+
Description string
28+
ImageURL string
29+
}
30+
2431
type TemplateVars struct {
2532
ByTime map[time.Time][]ShowtimeTemplateVars
33+
Details map[string]DetailsTemplateVars
2634
GoogleAnalytics string
2735
}
2836

@@ -35,6 +43,10 @@ func (r HtmlRenderer) Render(entries []model.ShowtimeEntry) error {
3543

3644
buckets := make(map[time.Time][]ShowtimeTemplateVars, 1)
3745
bucketTime := time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)
46+
47+
details := make(map[string]DetailsTemplateVars, 1)
48+
emptyDetails := model.ShowtimeDetails{}
49+
3850
for _, entry := range entries {
3951
showtime := entry.Showtime.When
4052
if showtime.Sub(bucketTime) > (time.Hour * 27) {
@@ -44,15 +56,23 @@ func (r HtmlRenderer) Render(entries []model.ShowtimeEntry) error {
4456
}
4557
showtimeIso := showtime.Format(time.RFC3339)
4658
showtimeDisplay := showtime.Format("15:04")
47-
searchIndex := strings.ToLower(strings.Join(append(
48-
strings.Fields(entry.Film),
49-
strings.Fields(entry.Theatre)...,
50-
), " "))
59+
filmKey := strings.ToLower(strings.Join(strings.Fields(entry.Film), " "))
60+
theatreKey := strings.ToLower(strings.Join(strings.Fields(entry.Theatre), " "))
61+
searchIndex := filmKey + " " + theatreKey
5162
buckets[bucketTime] = append(buckets[bucketTime], ShowtimeTemplateVars{
5263
Theatre: entry.Theatre, Film: entry.Film, ShowtimeISO: showtimeIso,
53-
ShowtimeDisplay: showtimeDisplay, Language: entry.Language, Search: searchIndex,
64+
ShowtimeDisplay: showtimeDisplay, Language: entry.Language,
65+
SearchIndex: searchIndex, DetailsIndex: filmKey,
5466
DeepLink: entry.DeepLink,
5567
})
68+
69+
if entry.Details != emptyDetails {
70+
details[filmKey] = DetailsTemplateVars{
71+
Film: entry.Film,
72+
Description: entry.Details.Description,
73+
ImageURL: entry.Details.ImageURL,
74+
}
75+
}
5676
}
5777

5878
err = os.MkdirAll("public", os.FileMode(int32(0755)))
@@ -66,7 +86,7 @@ func (r HtmlRenderer) Render(entries []model.ShowtimeEntry) error {
6686
return err
6787
}
6888

69-
err = t.Execute(outFile, TemplateVars{buckets, ""})
89+
err = t.Execute(outFile, TemplateVars{ByTime: buckets, Details: details, GoogleAnalytics: ""})
7090
if err != nil {
7191
return err
7292
}

‎internal/scraper/elevent.go

+21-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
type eleventScraper struct {
1515
Permalink string
16-
VenueID string
16+
VenueIDs []string
1717
}
1818

1919
type EleventShowtime struct {
@@ -35,7 +35,7 @@ type EleventEventListing struct {
3535
func (s eleventScraper) Scrape(ch chan<- []model.Showtime, dates []time.Time, tz *time.Location) error {
3636
showtimes := make([]model.Showtime, 0)
3737

38-
url := fmt.Sprintf("https://www.goelevent.com/%v/e/List?v=%v", s.Permalink, s.VenueID)
38+
url := fmt.Sprintf("https://www.goelevent.com/%v/e/List", s.Permalink)
3939
// TODO: more idiomatic way?
4040
res, err := http.Get(url)
4141
if err != nil {
@@ -48,6 +48,8 @@ func (s eleventScraper) Scrape(ch chan<- []model.Showtime, dates []time.Time, tz
4848
return err
4949
}
5050

51+
var venueIds = s.VenueIDs
52+
5153
doc.Find("#event-search-list-module").Each(func(i int, s *goquery.Selection) {
5254
modelStr, exists := s.Attr("model")
5355
var eventList = EleventEventListing{}
@@ -56,6 +58,12 @@ func (s eleventScraper) Scrape(ch chan<- []model.Showtime, dates []time.Time, tz
5658

5759
for _, event := range eventList.Events {
5860
for _, showtime := range event.Schedule {
61+
for venueID := range venueIds {
62+
if showtime.VenueID != venueID {
63+
break
64+
}
65+
}
66+
5967
when, err := time.Parse("2006-01-02T15:04:05", showtime.StartDateTime)
6068
localizedWhen := time.Date(when.Year(), when.Month(), when.Day(), when.Hour(), when.Minute(), 0, 0, tz)
6169
if err != nil {
@@ -98,11 +106,19 @@ func init() {
98106
if !ok {
99107
return nil, fmt.Errorf("missing required param: %v", "permalink")
100108
}
101-
venueID, ok := args["venueID"].(string)
109+
rawVenueIDs, ok := args["venueIDs"].([]interface{})
102110
if !ok {
103-
return nil, fmt.Errorf("missing required param: %v", "venueID")
111+
return nil, fmt.Errorf("missing required param: %v", "venueIDs")
112+
}
113+
venueIDs := make([]string, 1)
114+
for _, v := range rawVenueIDs {
115+
venueID, ok := v.(string)
116+
if !ok {
117+
return nil, fmt.Errorf("malformed venueID, must be string")
118+
}
119+
venueIDs = append(venueIDs, venueID)
104120
}
105121

106-
return &eleventScraper{permalink, venueID}, nil
122+
return &eleventScraper{permalink, venueIDs}, nil
107123
})
108124
}

‎public/css/styles.css

+47-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ a {
2525
color: inherit;
2626
}
2727

28-
a[data-goto-page] {
28+
a[data-goto-page], a[data-goto-details] {
2929
cursor: pointer;
3030
}
3131

@@ -98,3 +98,49 @@ header {
9898
color: #2e8ab1;
9999
letter-spacing: .8rem;
100100
}
101+
102+
.details {
103+
background: #343d46;
104+
display: none;
105+
position: fixed;
106+
top: 0;
107+
left: 0;
108+
width: 100%;
109+
height: 100%;
110+
display: none;
111+
}
112+
113+
.details.active {
114+
display: flex;
115+
}
116+
117+
.details-wrapper {
118+
display: flex;
119+
flex-direction: column;
120+
max-width: 96rem;
121+
margin: 0 auto;
122+
overflow-y: scroll;
123+
}
124+
125+
.details-controls {
126+
padding: 2rem 0;
127+
text-align: right;
128+
} .details-controls button {
129+
/* Disable for now; it is only a visual cue and may not be needed at all */
130+
display: none;
131+
}
132+
133+
.showtime-description {
134+
font-size: 3.6rem;
135+
margin: 2.5rem 0 5rem;
136+
color: #2e8ab1;
137+
}
138+
139+
.details .showtime-title {
140+
margin-top: 2rem;
141+
}
142+
143+
.details .showtime-image {
144+
width: 100%;
145+
object-fit: scale-down;
146+
}

‎public/index.tmpl

+14-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
<input type="text" placeholder="Filter by film/kino"/>
2020
</div>
2121
{{- define "showtime" -}}
22-
<li class="showtime" data-iso-time="{{ .ShowtimeISO }}" data-text-search="{{ .Search }}">
22+
<li class="showtime" data-iso-time="{{ .ShowtimeISO }}" data-text-search="{{ .SearchIndex }}">
2323
<div class="showtime-time">{{ .ShowtimeDisplay }}</div>
2424
<div class="showtime-info">
2525
<div class="showtime-title">
26-
{{if .DeepLink}}<a href="{{ .DeepLink }}" target="_blank">{{end}}{{ .Film }}{{if .DeepLink}}</a>{{end}}
26+
<a {{ if .DeepLink }}href="{{ .DeepLink }}" target="_blank"{{ end }} data-goto-details="{{ .DetailsIndex }}">{{ .Film }}</a>
2727
{{- if .Language}}
2828
<span class="showtime-language">{{ .Language }}</span>
2929
{{end -}}
@@ -45,6 +45,18 @@
4545
</div>
4646
{{- $classList = "inactive" -}}
4747
{{end}}
48+
{{ range $key, $details := .Details }}
49+
<div class="details" data-details="{{ $key }}">
50+
<div class="details-wrapper">
51+
<div class="details-controls">
52+
<button class="close">Close</button>
53+
</div>
54+
{{ if $details.ImageURL }}<img class="showtime-image" src="{{ $details.ImageURL }}" />{{ end }}
55+
<h3 class="showtime-title">{{ $details.Film }}</h3>
56+
<div class="showtime-description">{{ $details.Description }}</div>
57+
</div>
58+
</div>
59+
{{ end }}
4860
</div>
4961
<script async src="./js/page.js"></script>
5062
{{if .GoogleAnalytics}}

‎public/js/page.js

+25
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
var showtimeEls = doc.querySelectorAll('.showtime');
77
var filterEl = doc.querySelector('.filter > input');
88
var gotoPageLinks = doc.querySelectorAll('[data-goto-page]');
9+
var gotoDetailsLinks = doc.querySelectorAll('[data-goto-details]');
910

1011
// Hide any events in the past
1112
showtimeEls.forEach(function (el) {
@@ -46,6 +47,30 @@
4647
});
4748
});
4849
});
50+
51+
// Allow toggling details
52+
gotoDetailsLinks.forEach(function (linkEl) {
53+
var detailsKey = linkEl.dataset.gotoDetails;
54+
55+
function closer(el) {
56+
var handler = function (event) {
57+
el.classList.toggle('active', false);
58+
el.removeEventListener('click', handler);
59+
event.preventDefault();
60+
}
61+
return handler;
62+
}
63+
64+
linkEl.addEventListener('click', function (event) {
65+
doc.querySelectorAll('[data-details]').forEach(function (detailsEl) {
66+
var isActive = detailsEl.dataset.details === detailsKey;
67+
detailsEl.classList.toggle('active', isActive);
68+
if (isActive) {
69+
detailsEl.addEventListener('click', closer(detailsEl));
70+
}
71+
});
72+
});
73+
});
4974
}
5075

5176
if (doc.readyState !== 'loading') {

0 commit comments

Comments
 (0)
Please sign in to comment.