diff options
-rw-r--r-- | README.md | 41 | ||||
-rw-r--r-- | index.html.erb | 136 | ||||
-rw-r--r-- | lib/events.rb | 28 | ||||
-rw-r--r-- | lib/room-schedule.rb (renamed from room-schedule.rb) | 0 | ||||
-rw-r--r-- | program.ics.erb | 17 | ||||
-rwxr-xr-x[-rw-r--r--] | ramaskrik-program.rb | 106 |
6 files changed, 293 insertions, 35 deletions
@@ -4,6 +4,47 @@ Et lite script jeg laget for å gjøre det enklere for meg selv å få oversikt Siden som genereres av scriptet krever en nettleser med SVG 1.1 støtte, noe [de fleste](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject#Browser_compatibility) skulle ha i dag. +## Bruksanvisning + +Programmet tar en `.json` fil med data som input. Dataene må være en json liste (array) hvor hvert object i listen er en visning. Hver visning må ha følgende felter: + +- title: tekst, Filmens tittel +- venue: telst, Sal/auditorium/rom for visningen +- start\_time: tid+dato i et noenlunde maskinlesbart standardformat (ISO8601 er fint!) +- duration: Visningens lengde i sekunder +- image: url til filmplakat (valgfritt) + +Her er et eksempel fra en fil: + +```json +[ + { + "title":"Satanic Hispanics", + "venue":"Storsalen", + "start_time":"2023-10-19 10:30:00 +0200", + "duration":"6300", + "image":"https://mff.dx.no/132216.jpeg?w=270&h=480&fit=crop&auto=compress" + }, + { + ... + } +] +``` + +Kjør programmet slik: + +``` +% ruby ramaskrik-program.rb <input-fil.json> +``` + +Den skal da generere en `index.html` og en `program.ics` (kalender) fil i samme katalog. + +Last opp disse til et egnet sted på en webserver, og du har ditt eget ramaskrik program. + +Programmet kan selvsagt brukes til å lage andre tilsvarende programoversikter også. Bare +tilpass koden og malene etter ønske. + + ## LISENS Copyright (C) 2018 [Harald Eilertsen](haraldei@anduin.net) diff --git a/index.html.erb b/index.html.erb index f1dfbf0..6b8630e 100644 --- a/index.html.erb +++ b/index.html.erb @@ -1,12 +1,138 @@ <!DOCTYPE html> <html> <head> - <title>Ramaskrik 2018 programoversikt</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta charset="utf-8"> + <title><%= title %></title> +<style> +.events { + display:flex; + justify-content: space-around; + width: 800px; +} +.event-column { + width: 350px; +} +.event-column-inner, +.time-column-inner { + position: relative; +} +.timestamp { + position: absolute; + font-size: 10px; +} +.event-item { + display: flex; + position: absolute; + background-color: #b8afaf; + border: 1px solid #ccc; + padding: 0.3em; + color: #000; + box-sizing: border-box; + width: 100%; + + font-family: Helvetica; + font-size: small; +} +.event-item.selected { + background-color: #709270; +} +.event-item-image { + float: left; + margin-right: 0.3em; +} +.event-item-image img { + width: 3em; + object-fit: cover; +} +</style> </head> <body> - <h1>Ramaskrik 2018 programoversikt</h1> - <% generate_graphs do |graph| %> - <%= graph.burn_svg_only() %> - <% end %> + <h1><%= title %></h1> + <section id="introduction"> + <p>Dette er en annen visning av programmet for skrekkfilmfestivalen + <a href="https://ramaskrik.no">Ramaskrik 2023</a>. Jeg laget det for + meg selv, for å få en bedre oversikt over hvilke filmer som går hvor og når. Offisielt program finner du + på <a href="https://ramaskrik.no/program/">Ramaskrik sine egne websider</a>. Denne siden ble generert + <%= Date.today.strftime('%d.%m.%Y') %>, evt. endringer i programmet etter det er ikke tatt med.</p> + + <p>Du kan merke hvilke filmer du ønsker å se ved å klikke/tappe på de. Siden vil huske hvilke filmer du + har valgt, så du vil kunne bruke denne siden til å planlegge ruten din igjennom festivalen hvis du vil. + (Ingen data sendes til websiden, hvilke filmer du har valgt vil kun lagres i din egen nettleser.)</p> + + <p>Programmet er også tilgjengelig som en kalenderfil. <a href="./program-ramaskrik-2023.ics">Klikk her</a> + for å legge den til i kalenderen din</a>.</p> + + <p>Har du spørsmål, ta kontakt med meg <a href="mailto:haraldei-ramaprogram@anduin.net">via epost</a>.</p> + </section> + <section id="events-list"> + <% eventlist.each do |date, events| %> + <h2><%= date.strftime("%A %d.%m.%Y") %></h2> + <div class="events" style="height: <%= 200 + (events.end_time - events.start_time) / 50 %>px; width: <%= 100 + (events.venues.length * 350) %>px;"> + <% start_time = events.start_time.to_i %> + <% end_time = events.end_time.to_i %> + <% events.venues.sort.each do |venue| %> + <div class="time-column" style="position: relative;"> + <h3>Kl.</h3> + <div class="time-column-inner"> + <% (start_time..end_time).step(3600 / 4) do |time| %> + <div class="timestamp" style="top: <%= (time - start_time).to_i / 50 - 5 %>px; height: 10px;"> + <%= Time.at(time).strftime('%H:%M') %> + </div> + <% end %> + </div> + </div> + <div class="event-column"> + <h3><%= venue %></h2> + <div class="event-column-inner"> + <% events.events.select{ |e| e.venue == venue }.each do |e| %> + <div id="<%= e.slug %>" class="event-item" style="top: <%= e.offset %>px; height: <%= e.height %>px;"> + <div class="event-item-image"> + <% if e.image_url %> + <img src="<%= e.image_url %>"> + <% end %> + </div> + <div class="event-item-meta"> + <div class="event-item-title"><%= e.title %></div> + <div class="event-item-times"> + <time datetime="<%= e.start_time.iso8601 %>"> + <%= e.start_time.strftime('%H:%M') %> + </time> + - + <time datetime="<%= (e.start_time + e.duration).iso8601 %>"> + <%= (e.start_time + e.duration).strftime('%H:%M') %> + </time> + </div> + </div> + </div> + <% end %> + </div> + </div> + <% end %> + </div> + <% end %> + </section> + <section id="footer"> + <p>Denne siden ble generert den <%= DateTime.now.strftime('%d.%m.%Y kl. %H:%M') %> av + <a href="https://code.volse.net/ramaskrik-program.git/">Ramaskrik programoversikt-generator</a> + </section> </body> </html> +<script> + let event_items = document.getElementsByClassName('event-item'); + for (const element of event_items) { + if (localStorage.getItem(element.id) === 'selected') { + element.classList.add('selected'); + } + + element.addEventListener('click', event => { + const result = event.currentTarget.classList.toggle('selected'); + + if (result) { + localStorage.setItem(event.currentTarget.id, 'selected'); + } else { + localStorage.removeItem(event.currentTarget.id); + } + }); + } +</script> diff --git a/lib/events.rb b/lib/events.rb new file mode 100644 index 0000000..e647930 --- /dev/null +++ b/lib/events.rb @@ -0,0 +1,28 @@ +require 'time' + +module Events + class Event + + attr_reader :start_time + attr_reader :duration + attr_reader :title + attr_reader :venue + attr_reader :image_url + + def initialize(attrs) + @start_time = Time.parse(attrs['start_time']) + @duration = attrs['duration'].to_i + @title = attrs['title'] + @venue = attrs['venue'] + @image_url = attrs['image'] unless attrs['image'].length < 10 + end + + def date + @start_time.to_date + end + + def slug + "#{start_time.to_i}-#{title.downcase.gsub(/[^a-z0-9_-]/, '')}" + end + end +end diff --git a/room-schedule.rb b/lib/room-schedule.rb index fd4e282..fd4e282 100644 --- a/room-schedule.rb +++ b/lib/room-schedule.rb diff --git a/program.ics.erb b/program.ics.erb new file mode 100644 index 0000000..60c55bd --- /dev/null +++ b/program.ics.erb @@ -0,0 +1,17 @@ +BEGIN:VCALENDAR +PRODID:-//volse.net//NONSGML ramaskrik-program-generator//NB +VERSION:2.0 +DTSTAMP;TZID=Europe/Oslo:<%= DateTime.now.strftime('%Y%m%dT%H%M%S') %> +<% eventlist.each do |date, events| %> +<% events.events.each do |e| %> +BEGIN:VEVENT +UUID:<%= e.slug %> +DTSTAMP;TZID=Europe/Oslo:<%= DateTime.now.strftime('%Y%m%dT%H%M%S') %> +DTSTART;TZID=Europe/Oslo:<%= e.start_time.strftime('%Y%m%dT%H%M%S') %> +DURATION:PT<%= e.duration %>S +SUMMARY:<%= e.title %> +LOCATION:<%= e.venue %> +END:VEVENT +<% end %> +<% end %> +END:VCALENDAR diff --git a/ramaskrik-program.rb b/ramaskrik-program.rb index 3c56edc..5dfe5d5 100644..100755 --- a/ramaskrik-program.rb +++ b/ramaskrik-program.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + # Ramaskrik Program Schedule plotter. # Copyright (C) 2018 Harald Eilertsen <haraldei@anduin.net> # @@ -14,42 +16,26 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +$LOAD_PATH << File.join(File.dirname( __FILE__ ), "lib") + require 'date' +require 'delegate' require 'erb' +require 'events' +require 'json' +require 'scrapers/ramaskrik' require 'nokogiri' require 'open-uri' -require 'room-schedule' +#require 'room-schedule' require 'uri' -class Movie - attr_reader :title, :link, :start_time, :end_time, :venue - - def initialize(node) - @title = node.css("h4").text.strip - @link = URI.join("https://ramaskrik.no", node.css("h4 a").attribute('href').value) - @start_time = DateTime.parse(node.css(".date").attribute('title').value) - @end_time = DateTime.parse(@start_time.strftime('%F') + node.css(".prgtype-endTime").text.strip.sub(/\w+/, '')) - @end_time += 1 if @end_time < @start_time - @venue = node.css(".place").text.strip - end - - def to_s - "#{title} #{start_time} - #{end_time}, #{venue}" - end -end - -def scrape_program - doc = Nokogiri::HTML(open("https://ramaskrik.no/program/")) - doc.css(".kultur-type-movie").map { |movie| Movie.new(movie) } -end - -def generate_graphs - days = scrape_program.group_by { |movie| movie.start_time.strftime("%A %d.%m.%Y") } - days.map do |d, movies| - rooms = movies.group_by { |m| m.venue }.delete_if { |t, _| t == "Ramaskrik" } +def generate_graphs(events) + days = events.group_by { |event| event.start_time.strftime("%A %d.%m.%Y") } + days.map do |day, events| + rooms = events.group_by { |e| e.venue } graph = SVG::Graph::RoomSchedule.new({ - graph_title: d, + graph_title: day, show_graph_title: true, show_x_guidelines: true, width: 1280, @@ -67,6 +53,66 @@ def generate_graphs end end -t = ERB.new(IO.read("index.html.erb")) -IO.write("index.html", t.result(binding)) +class EventDecorator < SimpleDelegator + attr_reader :offset + attr_reader :height + + def calc_offsets(time_offset) + @offset = (start_time - time_offset).to_i / 50 + @height = duration / 50; + start_time + duration + end +end + +class SortedEventList + attr_reader :venues + attr_reader :events + attr_reader :start_time + attr_reader :end_time + + def initialize(events) + @venues = events.map{ |e| e.venue }.uniq + + @events = events + .sort{ |a,b| a.start_time - b.start_time } + .map{ |e| EventDecorator.new(e) } + + @start_time = @events.first.start_time - (@events.first.start_time.min * 60) + + @events.group_by{|e| e.venue}.each do |venue, events| + time_offset = start_time + time_end = start_time + events.each do |event| + time_end = event.calc_offsets(time_offset) + end + + @end_time = time_end if @end_time.nil? || @end_time < time_end + end + end +end + +def import_events_from_json(input) + JSON + .parse(input) + .map{ |obj| Events::Event.new(obj) } + .group_by{ |e| e.date } +end + +def make_sorted_event_lists_by_date(events_by_date) + new_list = {} + events_by_date.each do |date, events| + new_list[date] = SortedEventList.new(events) + end + + new_list +end + +title = "Ramaskrik 2023 - Program" + +eventlist = make_sorted_event_lists_by_date(import_events_from_json(IO.read(ARGV[0]))) + +t_html = ERB.new(IO.read("index.html.erb"), trim_mode: '>') +IO.write("index.html", t_html.result(binding)) +t_ics = ERB.new(IO.read("program.ics.erb"), trim_mode: '<>') +IO.write("program.ics", t_ics.result(binding).gsub(/\n/, "\r\n")) |