|
| 1 | +module Jekyll |
| 2 | + module TimeTableGenerator |
| 3 | + # タイムテーブル表を事前に計算してイベント表形式に変換 |
| 4 | + # これにより、Liquid テンプレートは単純な表示のみを担当 |
| 5 | + class Generator < Jekyll::Generator |
| 6 | + safe true |
| 7 | + priority :high |
| 8 | + |
| 9 | + # デフォルト設定値 |
| 10 | + DEFAULT_SLOT_MINUTES = 15 |
| 11 | + DEFAULT_DAY_START_HOUR = 10 # 10:00 |
| 12 | + DEFAULT_DAY_END_HOUR = 16 # 16:00 |
| 13 | + |
| 14 | + def generate(site) |
| 15 | + tt = site.data['time_table'] |
| 16 | + return unless tt |
| 17 | + |
| 18 | + # 設定値を取得 |
| 19 | + slot_minutes = tt.fetch('slot_minutes', DEFAULT_SLOT_MINUTES) |
| 20 | + day_start = tt.fetch('day_start_hour', DEFAULT_DAY_START_HOUR) |
| 21 | + day_end = tt.fetch('day_end_hour', DEFAULT_DAY_END_HOUR) |
| 22 | + rooms = tt.fetch('rooms', []) |
| 23 | + events = tt.fetch('events', []) |
| 24 | + room_styles = tt.fetch('room_styles', {}) |
| 25 | + |
| 26 | + # イベント情報を表形式で生成 |
| 27 | + time_table_events = create_event_table(events, rooms, room_styles, slot_minutes, day_start, day_end) |
| 28 | + |
| 29 | + # 生成したイベント表データを Liquid に提供 |
| 30 | + site.data['time_table_events'] = time_table_events |
| 31 | + end |
| 32 | + |
| 33 | + private |
| 34 | + |
| 35 | + def create_event_table(events, rooms, room_styles, slot_minutes, day_start, day_end) |
| 36 | + total_slots = ((day_end - day_start) * 60 / slot_minutes).to_i |
| 37 | + |
| 38 | + # 時間ラベルを生成 |
| 39 | + time_labels = (0...total_slots).map do |slot| |
| 40 | + minutes = day_start * 60 + slot * slot_minutes |
| 41 | + "#{minutes / 60}:%02d" % (minutes % 60) |
| 42 | + end |
| 43 | + |
| 44 | + # ルーム情報を生成(room.style でアクセス可能) |
| 45 | + rooms_data = rooms.map do |room_name| |
| 46 | + { |
| 47 | + 'name' => room_name, |
| 48 | + 'style' => room_styles[room_name] || {} |
| 49 | + } |
| 50 | + end |
| 51 | + |
| 52 | + # イベント表を生成(2次元配列) |
| 53 | + table = Array.new(total_slots) { Array.new(rooms.size) } |
| 54 | + |
| 55 | + events.each do |event| |
| 56 | + place_event(event, table, rooms, slot_minutes, day_start, total_slots) |
| 57 | + end |
| 58 | + |
| 59 | + { |
| 60 | + 'events' => table, |
| 61 | + 'rooms' => rooms_data, |
| 62 | + 'time_labels' => time_labels, |
| 63 | + 'total_slots' => total_slots - 1, # Liquidの (0..n) は inclusive なので -1 |
| 64 | + 'total_rooms' => rooms.size - 1, # Liquidの (0..n) は inclusive なので -1 |
| 65 | + } |
| 66 | + end |
| 67 | + |
| 68 | + def place_event(event, table, rooms, slot_minutes, day_start, total_slots) |
| 69 | + room_index = rooms.index(event['room']) |
| 70 | + return unless room_index |
| 71 | + |
| 72 | + # 時間を分に変換して揃える |
| 73 | + event_start = time_to_minutes(event['start']) |
| 74 | + event_end = time_to_minutes(event['end']) |
| 75 | + |
| 76 | + # スロット計算(分に揃える) |
| 77 | + slot_start = [(event_start - day_start * 60) / slot_minutes, 0].max.to_i |
| 78 | + slot_end = [(event_end - day_start * 60) / slot_minutes, total_slots].min.to_i |
| 79 | + duration = slot_end - slot_start |
| 80 | + |
| 81 | + return if slot_start >= total_slots || duration <= 0 |
| 82 | + |
| 83 | + # イベントの長さ情報 (duration) を追加 |
| 84 | + table[slot_start][room_index] = event.merge('duration' => duration) |
| 85 | + |
| 86 | + # 継続スロットをマーク |
| 87 | + (slot_start + 1...slot_end).each do |slot| |
| 88 | + break if slot >= total_slots |
| 89 | + table[slot][room_index] = 'continued' |
| 90 | + end |
| 91 | + end |
| 92 | + |
| 93 | + def time_to_minutes(time_str) |
| 94 | + return 0 unless time_str |
| 95 | + hours, minutes = time_str.split(':').map(&:to_i) |
| 96 | + hours * 60 + minutes |
| 97 | + end |
| 98 | + end |
| 99 | + end |
| 100 | +end |
0 commit comments