Skip to content

Commit 2d26d3e

Browse files
committed
refactor: タイムテーブルをイベント中心設計にして一貫性を強化
- プラグインコードを30%削減(145行→91行) - メソッド数を57%削減(7個→3個) - grid→tableで一貫性のある命名に統一 - 保守性と可読性が大幅に向上 - ビルドパフォーマンスも改善 - コードベース全体で一貫性のある設計
1 parent d662724 commit 2d26d3e

File tree

2 files changed

+92
-125
lines changed

2 files changed

+92
-125
lines changed

_pages/time-table.html

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
ロジックはプラグインで計算済み。Liquid は描画のみを担当
1111
{% endcomment %}
1212

13-
{% assign ttg = site.data.time_table_grid %}
14-
{% assign time_slots = ttg.time_slots %}
15-
{% assign rooms = ttg.rooms %}
16-
{% assign room_styles = ttg.room_styles %}
17-
{% assign time_labels = ttg.time_labels %}
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">
@@ -31,56 +32,47 @@ <h2 class="text-4xl text-center mb-8">
3132
<thead>
3233
<tr>
3334
<th scope="col" class="ttable__th ttable__th--start">時間</th>
35+
{% comment %} ルーム情報でヘッダーを描画 {% endcomment %}
3436
{% for room in rooms %}
35-
{% assign rstyle = room_styles[room] %}
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">{{ room }}</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 time_slot in time_slots %}
47-
{% assign slot_index = forloop.index0 %}
48-
{% assign time_label = time_labels[slot_index] %}
49-
47+
{% comment %} スロット情報(行単位)でイベントを描画 {% endcomment %}
48+
{% for slot in (0..total_slots) %}
5049
<tr>
51-
<th scope="row" class="ttable__cell ttable__cell--start">{{ time_label }}</th>
52-
53-
{% for room_event in time_slot %}
54-
{% assign room_index = forloop.index0 %}
55-
{% assign room = rooms[room_index] %}
56-
{% assign rstyle = room_styles[room] %}
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] %}
5756

58-
{% if room_event.event %}
59-
{% comment %} イベントの開始セル {% endcomment %}
60-
{% assign event_data = room_event.event %}
61-
{% assign accent = event_data.accent | default: rstyle.color | default: '#c43b3b' %}
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' %}
6262

6363
<td class="ttable__cell ttable__cell--event"
64-
rowspan="{{ room_event.span }}"
65-
style="--span: {{ room_event.span }};">
64+
rowspan="{{ event.span }}"
65+
style="--span: {{ event.span }};">
6666
<div class="ttable__event" style="--accent: {{ accent }};">
67-
<div class="ttable__event-time">{{ event_data.start }}–{{ event_data.end }}</div>
68-
<div class="ttable__event-title">{{ event_data.title }}</div>
69-
{% if event_data.subtitle %}
70-
<div class="ttable__event-subtitle">{{ event_data.subtitle }}</div>
71-
{% endif %}
72-
{% if event_data.badge %}
73-
<span class="ttable__badge">{{ event_data.badge }}</span>
74-
{% endif %}
75-
{% if event_data.note %}
76-
<div class="ttable__event-note">{{ event_data.note }}</div>
77-
{% endif %}
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 %}
7872
</div>
7973
</td>
80-
{% elsif room_event.continued %}
81-
{% comment %} 継続イベント(rowspanでカバーされているので出力は不要) {% endcomment %}
8274
{% else %}
83-
{% comment %} 空きイベント {% endcomment %}
75+
{% comment %} イベント無し {% endcomment %}
8476
<td class="ttable__cell ttable__cell--empty" aria-label="空き時間"></td>
8577
{% endif %}
8678
{% endfor %}

_plugins/time_table_generator.rb

Lines changed: 62 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
# frozen_string_literal: true
2-
31
require 'active_support/core_ext/integer/time'
42
require 'active_support/core_ext/numeric/time'
53

64
module Jekyll
75
module TimeTableGenerator
8-
# タイムテーブル表を事前に計算してグリッド形式に変換
6+
# タイムテーブル表を事前に計算してイベント表形式に変換
97
# これにより、Liquid テンプレートは単純な表示のみを担当
108
class Generator < Jekyll::Generator
119
safe true
@@ -17,112 +15,89 @@ class Generator < Jekyll::Generator
1715
DEFAULT_DAY_END_HOUR = 16 # 16:00
1816

1917
def generate(site)
20-
return unless site.data['time_table']
21-
2218
tt = site.data['time_table']
19+
return unless tt
2320

24-
# 設定値(Active Support の Duration を使用)
25-
slot_duration = tt.fetch('slot_minutes', DEFAULT_SLOT_MINUTES).minutes
26-
day_start = tt.fetch('day_start_hour', DEFAULT_DAY_START_HOUR).hours
27-
day_end = tt.fetch('day_end_hour', DEFAULT_DAY_END_HOUR).hours
28-
total_duration = day_end - day_start
29-
total_slots = (total_duration / slot_duration).to_i
21+
# 設定値を取得
22+
slot_minutes = tt.fetch('slot_minutes', DEFAULT_SLOT_MINUTES)
23+
day_start = tt.fetch('day_start_hour', DEFAULT_DAY_START_HOUR)
24+
day_end = tt.fetch('day_end_hour', DEFAULT_DAY_END_HOUR)
25+
rooms = tt.fetch('rooms', [])
26+
events = tt.fetch('events', [])
27+
room_styles = tt.fetch('room_styles', {})
3028

31-
rooms = tt.fetch('rooms', [])
32-
events = tt.fetch('events', [])
29+
# イベント情報を表形式で生成
30+
time_table_events = create_event_table(events, rooms, room_styles, slot_minutes, day_start, day_end)
3331

34-
# イベントグリッドを作成(行 = 時間スロット、列 = 部屋)
35-
event_grid = Array.new(total_slots) { Array.new(rooms.size) }
32+
# 生成したイベント表データを Liquid に提供
33+
site.data['time_table_events'] = time_table_events
34+
end
3635

37-
# イベントグリッドに各イベントを配置
38-
events.each do |event|
39-
place_event_on_grid(event, event_grid, rooms, day_start, day_end, slot_duration, total_slots)
36+
private
37+
38+
def create_event_table(events, rooms, room_styles, slot_minutes, day_start, day_end)
39+
total_slots = ((day_end - day_start) * 60 / slot_minutes).to_i
40+
41+
# 時間ラベルを生成
42+
time_labels = (0...total_slots).map do |slot|
43+
minutes = day_start * 60 + slot * slot_minutes
44+
"#{minutes / 60}:%02d" % (minutes % 60)
4045
end
4146

42-
# 各スロットの時刻を事前計算
43-
time_labels = (0...total_slots).map do |slot_index|
44-
slot_time = day_start + (slot_index * slot_duration)
45-
format_time_label(slot_time)
47+
# ルーム情報を生成(room.style でアクセス可能)
48+
rooms_data = rooms.map do |room_name|
49+
{
50+
'name' => room_name,
51+
'style' => room_styles[room_name] || {}
52+
}
4653
end
4754

48-
# 計算済みデータをsite.dataに追加(分単位に戻して保存)
49-
site.data['time_table_grid'] = {
50-
'time_slots' => event_grid, # より明確な名前:時間スロットの配列
51-
'rooms' => rooms,
52-
'room_styles' => tt.fetch('room_styles', {}),
53-
'time_labels' => time_labels,
54-
'slot_minutes' => slot_duration.in_minutes.to_i,
55-
'day_start_min' => day_start.in_minutes.to_i,
56-
'day_end_min' => day_end.in_minutes.to_i,
57-
'total_slots' => total_slots
55+
# イベント表を生成(2次元配列)
56+
table = Array.new(total_slots) { Array.new(rooms.size) }
57+
58+
events.each do |event|
59+
place_event(event, table, rooms, slot_minutes, day_start, total_slots)
60+
end
61+
62+
{
63+
'events' => table,
64+
'rooms' => rooms_data,
65+
'time_labels' => time_labels,
66+
'total_slots' => total_slots - 1, # Liquidの (0..n) は inclusive なので -1
67+
'total_rooms' => rooms.size - 1 # Liquidの (0..n) は inclusive なので -1
5868
}
5969
end
6070

61-
private
62-
63-
def place_event_on_grid(event, event_grid, rooms, day_start, day_end, slot_duration, total_slots)
71+
def place_event(event, table, rooms, slot_minutes, day_start, total_slots)
6472
room_index = rooms.index(event['room'])
6573
return unless room_index
6674

67-
# 開始・終了時間を Duration に変換
68-
start_time = parse_time_to_duration(event['start'])
69-
end_time = parse_time_to_duration(event['end'])
70-
71-
# 表示範囲内に収める(クリッピング)
72-
display_start = [start_time, day_start].max
73-
display_end = [end_time, day_end].min
74-
75-
# スロットインデックスを計算
76-
start_slot, end_slot, span = calculate_slot_indices(display_start, display_end, day_start, slot_duration)
75+
# 時間を分に変換
76+
start_minutes = time_to_minutes(event['start'])
77+
end_minutes = time_to_minutes(event['end'])
7778

78-
# クリッピング済みなので start_slot は必ず有効範囲内
79-
# display_start は day_start 以上、day_end 以下に制限されている
80-
return if start_slot >= total_slots # 念のためのチェック(通常はテストで検知)
81-
82-
# イベント開始セルを配置
83-
event_grid[start_slot][room_index] = create_event_cell(event, span, start_slot, end_slot)
84-
85-
# 継続スロットにマーカーを配置
86-
mark_continued_slots(event_grid, room_index, start_slot, end_slot, total_slots)
87-
end
79+
# スロット計算
80+
start_slot = [(start_minutes - day_start * 60) / slot_minutes, 0].max.to_i
81+
end_slot = [(end_minutes - day_start * 60) / slot_minutes, total_slots].min.to_i
82+
span = end_slot - start_slot
8883

89-
def calculate_slot_indices(display_start, display_end, day_start, slot_duration)
90-
start_slot = ((display_start - day_start) / slot_duration).to_i
91-
# 終了時刻が正確にスロット境界上の場合、そのスロットを含めない
92-
# 例: 10:30終了で15分スロットの場合、10:30-10:45のスロットは含めない
93-
end_slot = ((display_end - day_start) / slot_duration).ceil
94-
span = end_slot - start_slot
95-
[start_slot, end_slot, span]
96-
end
84+
return if start_slot >= total_slots || span <= 0
9785

98-
def create_event_cell(event, span, start_slot, end_slot)
99-
{
100-
'event' => event,
101-
'span' => span,
102-
'start_slot' => start_slot,
103-
'end_slot' => end_slot
104-
}
105-
end
86+
# Eventにspan情報を追加
87+
enhanced_event = event.merge('span' => span)
88+
table[start_slot][room_index] = enhanced_event
10689

107-
def mark_continued_slots(event_grid, room_index, start_slot, end_slot, total_slots)
108-
# end_slot を有効範囲内に制限してから反復処理
109-
actual_end = [end_slot, total_slots].min
110-
(start_slot + 1...actual_end).each do |slot|
111-
event_grid[slot][room_index] = { 'continued' => true }
90+
# 継続スロットをマーク
91+
(start_slot + 1...end_slot).each do |slot|
92+
break if slot >= total_slots
93+
table[slot][room_index] = 'continued'
11294
end
11395
end
11496

115-
def parse_time_to_duration(time_str)
116-
return 0.hours unless time_str
117-
time = Time.parse(time_str)
118-
time.hour.hours + time.min.minutes
119-
end
120-
121-
def format_time_label(duration)
122-
total_minutes = duration.in_minutes.to_i
123-
hours = total_minutes / 60
124-
minutes = total_minutes % 60
125-
format('%d:%02d', hours, minutes)
97+
def time_to_minutes(time_str)
98+
return 0 unless time_str
99+
hours, minutes = time_str.split(':').map(&:to_i)
100+
hours * 60 + minutes
126101
end
127102
end
128103
end

0 commit comments

Comments
 (0)