Skip to content

Commit 80e7720

Browse files
Merge pull request #81 from coderdojo-japan/refactor-time-table-with-plugin
refactor: タイムテーブルの計算処理と描画処理を分離し、それぞれの役割を明確化
2 parents 86e39e9 + 4c5e3c0 commit 80e7720

File tree

2 files changed

+142
-80
lines changed

2 files changed

+142
-80
lines changed

_pages/time-table.html

Lines changed: 42 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55
---
66
{% include navbar.html %}
77

8-
{% assign tt = site.data.time_table %}
9-
{% assign slot = tt.slot_minutes | default: 15 %}
10-
{% assign rooms = tt.rooms %}
11-
{% assign room_count = rooms | size %}
8+
{% comment %}
9+
Jekyll プラグインで事前計算されたタイムテーブル表を使用
10+
ロジックはプラグインで計算済み。Liquid は描画のみを担当
11+
{% endcomment %}
1212

13-
{% assign day_start_min = 600 %}
14-
{% assign day_end_min = 960 %}
15-
{% assign total_minutes = day_end_min | minus: day_start_min %}
16-
{% assign slots_count = total_minutes | divided_by: slot %}
17-
{% assign last_row = slots_count | minus: 1 %}
13+
{% assign tte = site.data.time_table_events %}
14+
{% assign events = tte.events %}
15+
{% assign rooms = tte.rooms %}
16+
{% assign time_labels = tte.time_labels %}
17+
{% assign total_slots = tte.total_slots %}
18+
{% assign total_rooms = tte.total_rooms %}
1819

1920
<section class="max-w-[1200px] mx-auto px-4 sm:px-8 mt-30 xl:mt-15">
2021
<h2 class="text-4xl text-center mb-8">
@@ -23,94 +24,55 @@ <h2 class="text-4xl text-center mb-8">
2324
</h2>
2425

2526
<div class="ttable-wrap" aria-label="タイムテーブル(横スクロール可)">
26-
<table class="ttable" style="--room-count: {{ room_count }};">
27+
<table class="ttable" style="--room-count: {{ rooms | size }};">
2728
<caption>
28-
{{ tt.date | default: site.date_event }} のタイムテーブル
29+
{{ site.date_event }} のタイムテーブル
2930
</caption>
3031

3132
<thead>
3233
<tr>
3334
<th scope="col" class="ttable__th ttable__th--start">時間</th>
34-
{% for r in rooms %}
35-
{% assign rstyle = tt.room_styles[r] %}
35+
{% comment %} ルーム単位でヘッダーを描画 {% endcomment %}
36+
{% for room in rooms %}
3637
<th scope="col"
3738
class="ttable__th ttable__th--room"
38-
style="--room-color: {{ rstyle.color | default: '#c43b3b' }};">
39-
<span class="ttable__room-cap">{{ r }}</span>
39+
style="--room-color: {{ room.style.color | default: '#c43b3b' }};">
40+
<span class="ttable__room-cap">{{ room.name }}</span>
4041
</th>
4142
{% endfor %}
4243
</tr>
4344
</thead>
4445

4546
<tbody>
46-
{% for i in (0..last_row) %}
47-
{% assign row_min = i | times: slot | plus: day_start_min %}
48-
{% assign h = row_min | divided_by: 60 %}
49-
{% assign mf = row_min | modulo: 60 | plus: 0 | prepend: '0' | slice: -2, 2 %}
50-
47+
{% comment %} スロット単位(行単位)でイベントを描画 {% endcomment %}
48+
{% for slot in (0..total_slots) %}
5149
<tr>
52-
<!-- 左1列(sticky) -->
53-
<th scope="row" class="ttable__cell ttable__cell--start">{{ h }}:{{ mf }}</th>
54-
55-
{% for r in rooms %}
56-
{% assign rstyle = tt.room_styles[r] %}
57-
{% assign events_in_room = tt.events | where: 'room', r | sort: 'start' %}
58-
59-
{% assign active_event = nil %}
60-
{% assign active_event_start_index = nil %}
61-
62-
{% for ev in events_in_room %}
63-
{% assign s_h = ev.start | split: ':' | first | plus: 0 %}
64-
{% assign s_m = ev.start | split: ':' | last | plus: 0 %}
65-
{% assign e_h = ev.end | split: ':' | first | plus: 0 %}
66-
{% assign e_m = ev.end | split: ':' | last | plus: 0 %}
67-
{% assign s_min = s_h | times: 60 | plus: s_m %}
68-
{% assign e_min = e_h | times: 60 | plus: e_m %}
69-
70-
{% assign s_clamped = s_min %}
71-
{% if s_clamped < day_start_min %}{% assign s_clamped = day_start_min %}{% endif %}
72-
{% assign e_clamped = e_min %}
73-
{% if e_clamped > day_end_min %}{% assign e_clamped = day_end_min %}{% endif %}
74-
75-
{% assign span_minutes = e_clamped | minus: s_clamped %}
76-
{% if span_minutes > 0 %}
77-
{% assign numerator = span_minutes | plus: slot | minus: 1 %}
78-
{% assign span_slots = numerator | divided_by: slot %}
79-
{% assign s_index = s_clamped | minus: day_start_min | divided_by: slot %}
80-
{% assign e_index = s_index | plus: span_slots %}
81-
{% if i >= s_index and i < e_index %}
82-
{% assign active_event = ev %}
83-
{% assign active_event_start_index = s_index %}
84-
{% endif %}
85-
{% endif %}
86-
{% endfor %}
87-
88-
{% if active_event and i == active_event_start_index %}
89-
{%- assign s_h = active_event.start | split: ':' | first | plus: 0 -%}
90-
{%- assign s_m = active_event.start | split: ':' | last | plus: 0 -%}
91-
{%- assign e_h = active_event.end | split: ':' | first | plus: 0 -%}
92-
{%- assign e_m = active_event.end | split: ':' | last | plus: 0 -%}
93-
{%- assign s_min = s_h | times: 60 | plus: s_m -%}
94-
{%- assign e_min = e_h | times: 60 | plus: e_m -%}
95-
{%- if s_min < day_start_min -%}{%- assign s_min = day_start_min -%}{%- endif -%}
96-
{%- if e_min > day_end_min -%}{%- assign e_min = day_end_min -%}{%- endif -%}
97-
{%- assign span_minutes = e_min | minus: s_min -%}
98-
{%- assign numerator = span_minutes | plus: slot | minus: 1 -%}
99-
{%- assign span_slots = numerator | divided_by: slot -%}
100-
{%- assign accent = active_event.accent | default: rstyle.color -%}
101-
<td class="ttable__cell ttable__cell--event" rowspan="{{ span_slots }}" style="--span: {{ span_slots }};">
102-
<div class="ttable__event" style="--accent: {{ accent | default: '#c43b3b' }};">
103-
<div class="ttable__event-time">{{ active_event.start }}–{{ active_event.end }}</div>
104-
<div class="ttable__event-title">{{ active_event.title }}</div>
105-
{% if active_event.subtitle %}
106-
<div class="ttable__event-subtitle">{{ active_event.subtitle }}</div>
107-
{% endif %}
108-
{% if active_event.badge %}
109-
<span class="ttable__badge">{{ active_event.badge }}</span>
110-
{% endif %}
50+
<th scope="row" class="ttable__cell ttable__cell--start">{{ time_labels[slot] }}</th>
51+
52+
{% comment %} 各イベントを描画 {% endcomment %}
53+
{% for room_index in (0..total_rooms) %}
54+
{% assign event = events[slot][room_index] %}
55+
{% assign room = rooms[room_index] %}
56+
57+
{% if event == 'continued' %}
58+
{% comment %} イベント継続中 (rowspan で描画するため出力は不要) {% endcomment %}
59+
{% elsif event %}
60+
{% comment %} イベントを描画 {% endcomment %}
61+
{% assign accent = event.accent | default: room.style.color | default: '#c43b3b' %}
62+
63+
<td class="ttable__cell ttable__cell--event"
64+
rowspan="{{ event.duration }}"
65+
style="--span: {{ event.duration }};">
66+
<div class="ttable__event" style="--accent: {{ accent }};">
67+
<div class="ttable__event-time" >{{ event.start }}–{{ event.end }}</div>
68+
<div class="ttable__event-title">{{ event.title }}</div>
69+
{% if event.subtitle %}<div class="ttable__event-subtitle">{{ event.subtitle }}</div>{% endif %}
70+
{% if event.badge %}<span class="ttable__badge">{{ event.badge }}</span>{% endif %}
71+
{% if event.note %}<div class="ttable__event-note">{{ event.note }}</div>{% endif %}
11172
</div>
11273
</td>
113-
{% elsif active_event == nil %}
74+
{% else %}
75+
{% comment %} イベント無し {% endcomment %}
11476
<td class="ttable__cell ttable__cell--empty" aria-label="空き時間"></td>
11577
{% endif %}
11678
{% endfor %}

_plugins/time_table_generator.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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

Comments
 (0)