daigaku 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/bin/daigaku +12 -0
- data/daigaku.gemspec +37 -0
- data/lib/daigaku.rb +27 -0
- data/lib/daigaku/chapter.rb +23 -0
- data/lib/daigaku/configuration.rb +86 -0
- data/lib/daigaku/course.rb +23 -0
- data/lib/daigaku/database.rb +64 -0
- data/lib/daigaku/exceptions.rb +19 -0
- data/lib/daigaku/generator.rb +53 -0
- data/lib/daigaku/loadable.rb +23 -0
- data/lib/daigaku/loading/chapters.rb +9 -0
- data/lib/daigaku/loading/courses.rb +9 -0
- data/lib/daigaku/loading/units.rb +9 -0
- data/lib/daigaku/reference_solution.rb +10 -0
- data/lib/daigaku/solution.rb +42 -0
- data/lib/daigaku/task.rb +10 -0
- data/lib/daigaku/terminal.rb +11 -0
- data/lib/daigaku/terminal/cli.rb +59 -0
- data/lib/daigaku/terminal/courses.rb +114 -0
- data/lib/daigaku/terminal/output.rb +72 -0
- data/lib/daigaku/terminal/setup.rb +115 -0
- data/lib/daigaku/terminal/solutions.rb +46 -0
- data/lib/daigaku/terminal/texts/about.txt +19 -0
- data/lib/daigaku/terminal/texts/courses_empty.txt +3 -0
- data/lib/daigaku/terminal/texts/hint_course_download.txt +13 -0
- data/lib/daigaku/terminal/texts/welcome.txt +12 -0
- data/lib/daigaku/terminal/welcome.rb +98 -0
- data/lib/daigaku/test.rb +46 -0
- data/lib/daigaku/test_result.rb +69 -0
- data/lib/daigaku/unit.rb +28 -0
- data/lib/daigaku/version.rb +3 -0
- data/lib/daigaku/views.rb +59 -0
- data/lib/daigaku/views/chapters_menu.rb +91 -0
- data/lib/daigaku/views/courses_menu.rb +87 -0
- data/lib/daigaku/views/main_menu.rb +37 -0
- data/lib/daigaku/views/splash.rb +57 -0
- data/lib/daigaku/views/task_view.rb +206 -0
- data/lib/daigaku/views/top_bar.rb +48 -0
- data/lib/daigaku/views/units_menu.rb +92 -0
- data/lib/daigaku/window.rb +160 -0
- data/spec/daigaku/chapter_spec.rb +76 -0
- data/spec/daigaku/configuration_spec.rb +161 -0
- data/spec/daigaku/course_spec.rb +75 -0
- data/spec/daigaku/database_spec.rb +79 -0
- data/spec/daigaku/generator_spec.rb +82 -0
- data/spec/daigaku/loading/chapters_spec.rb +16 -0
- data/spec/daigaku/loading/courses_spec.rb +16 -0
- data/spec/daigaku/loading/units_spec.rb +21 -0
- data/spec/daigaku/reference_solution_spec.rb +23 -0
- data/spec/daigaku/solution_spec.rb +79 -0
- data/spec/daigaku/task_spec.rb +23 -0
- data/spec/daigaku/terminal/cli_spec.rb +51 -0
- data/spec/daigaku/terminal/courses_spec.rb +60 -0
- data/spec/daigaku/terminal/output_spec.rb +123 -0
- data/spec/daigaku/terminal/setup_spec.rb +10 -0
- data/spec/daigaku/terminal/solutions_spec.rb +8 -0
- data/spec/daigaku/terminal/welcome_spec.rb +12 -0
- data/spec/daigaku/terminal_spec.rb +14 -0
- data/spec/daigaku/test_example_spec.rb +54 -0
- data/spec/daigaku/test_result_spec.rb +81 -0
- data/spec/daigaku/test_spec.rb +48 -0
- data/spec/daigaku/unit_spec.rb +85 -0
- data/spec/daigaku/views/chapters_menu_spec.rb +8 -0
- data/spec/daigaku/views/courses_menu_spec.rb +8 -0
- data/spec/daigaku/views/task_view_spec.rb +7 -0
- data/spec/daigaku/views/units_menu_spec.rb +8 -0
- data/spec/daigaku/views_spec.rb +23 -0
- data/spec/daigaku_spec.rb +57 -0
- data/spec/path_helpers_spec.rb +60 -0
- data/spec/resource_helpers_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/macros/content_helpers.rb +129 -0
- data/spec/support/macros/mock_helpers.rb +20 -0
- data/spec/support/macros/path_helpers.rb +133 -0
- data/spec/support/macros/resource_helpers.rb +119 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- metadata +361 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
|
5
|
+
class CoursesMenu
|
6
|
+
include Views
|
7
|
+
include Wisper::Publisher
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@position = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def enter
|
14
|
+
@window = default_window
|
15
|
+
|
16
|
+
main_panel(@window) do |window|
|
17
|
+
show sub_window_below_top_bar(window)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def reenter_courses_menu(course)
|
22
|
+
@current_course = course
|
23
|
+
enter
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def show(window)
|
29
|
+
draw(window, @position)
|
30
|
+
interact_with(window)
|
31
|
+
end
|
32
|
+
|
33
|
+
def draw(window, active_index = 0)
|
34
|
+
window.attrset(A_NORMAL)
|
35
|
+
window.setpos(0, 1)
|
36
|
+
window.write 'Available daigaku courses:'
|
37
|
+
|
38
|
+
course_entries.each_with_index do |item, index|
|
39
|
+
window.setpos(index + 2, 1)
|
40
|
+
window.print_indicator(courses[index])
|
41
|
+
window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
|
42
|
+
window.write " #{item.to_s} "
|
43
|
+
end
|
44
|
+
|
45
|
+
window.refresh
|
46
|
+
end
|
47
|
+
|
48
|
+
def interact_with(window)
|
49
|
+
while char = window.getch
|
50
|
+
case char
|
51
|
+
when KEY_UP
|
52
|
+
@position -= 1
|
53
|
+
broadcast(:reset_menu_position)
|
54
|
+
when KEY_DOWN
|
55
|
+
@position += 1
|
56
|
+
broadcast(:reset_menu_position)
|
57
|
+
when 10 # Enter
|
58
|
+
broadcast(:enter_chapters_menu, courses[@position])
|
59
|
+
return
|
60
|
+
when 27 # ESC
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
entries = course_entries
|
65
|
+
@position = entries.length - 1 if @position < 0
|
66
|
+
@position = 0 if @position >= entries.length
|
67
|
+
draw(window, @position)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def courses
|
72
|
+
@courses = Loading::Courses.load(Daigaku.config.courses_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def course_entries
|
76
|
+
non_empty_courses = courses.select { |course| !course.chapters.empty? }
|
77
|
+
|
78
|
+
@course_entries = non_empty_courses.map do |course|
|
79
|
+
line = "#{course.title}"
|
80
|
+
line << "(#{course.author})" if course.author
|
81
|
+
line
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class MainMenu
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
courses_menu = Views::CoursesMenu.new
|
9
|
+
chapters_menu = Views::ChaptersMenu.new
|
10
|
+
units_menu = Views::UnitsMenu.new
|
11
|
+
task_view = Views::TaskView.new
|
12
|
+
|
13
|
+
# Subscription: `first.subscribe(second)` means
|
14
|
+
# first subscribes second on the first's broadcast.
|
15
|
+
# second has to have method that is broadcasted.
|
16
|
+
|
17
|
+
# top down navigation
|
18
|
+
courses_menu.subscribe(chapters_menu, on: :enter_chapters_menu)
|
19
|
+
chapters_menu.subscribe(units_menu, on: :enter_units_menu)
|
20
|
+
units_menu.subscribe(task_view, on: :enter_task_view)
|
21
|
+
|
22
|
+
# bottom up navigation
|
23
|
+
chapters_menu.subscribe(courses_menu, on: :reenter_courses_menu)
|
24
|
+
units_menu.subscribe(chapters_menu, on: :reenter_chapters_menu)
|
25
|
+
task_view.subscribe(units_menu, on: :reenter_units_menu)
|
26
|
+
|
27
|
+
# position reset
|
28
|
+
courses_menu.subscribe(chapters_menu, on: :reset_menu_position)
|
29
|
+
courses_menu.subscribe(units_menu, on: :reset_menu_position)
|
30
|
+
chapters_menu.subscribe(units_menu, on: :reset_menu_position)
|
31
|
+
|
32
|
+
courses_menu.enter
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class Splash
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
title = "DAIGAKU"
|
9
|
+
subtitle = "Learning the Ruby programming language dead easy."
|
10
|
+
|
11
|
+
panel = default_window
|
12
|
+
|
13
|
+
lines.times do |line|
|
14
|
+
panel.setpos(line, 0)
|
15
|
+
panel.red(' ' * cols, Curses::A_STANDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
panel.setpos((lines / 4), (cols - title.length) / 2)
|
19
|
+
panel.red(title, Curses::A_STANDOUT)
|
20
|
+
panel.refresh
|
21
|
+
|
22
|
+
sleep 0.5
|
23
|
+
|
24
|
+
ruby_ascii_art.each_with_index do |line, index|
|
25
|
+
panel.setpos(lines / 4 + 2 + index, (cols - line.length) / 2)
|
26
|
+
panel.red(line, Curses::A_STANDOUT)
|
27
|
+
sleep 0.06
|
28
|
+
panel.refresh
|
29
|
+
end
|
30
|
+
|
31
|
+
panel.setpos(lines / 4 + 11, (cols - subtitle.length) / 2)
|
32
|
+
|
33
|
+
subtitle.chars do |char|
|
34
|
+
panel.red(char, Curses::A_STANDOUT)
|
35
|
+
panel.refresh
|
36
|
+
sleep 0.02
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep 2.5
|
40
|
+
|
41
|
+
close_screen
|
42
|
+
end
|
43
|
+
|
44
|
+
def ruby_ascii_art
|
45
|
+
[
|
46
|
+
" ___________ ",
|
47
|
+
" /.\\ /.\\ /.\\ ",
|
48
|
+
"/___\\/___\\/___\\",
|
49
|
+
" \\ \\ . / . / ",
|
50
|
+
" \\ \\ ./ ./ ",
|
51
|
+
" \\\\ / / ",
|
52
|
+
" \\./ "
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
require 'os'
|
5
|
+
|
6
|
+
class TaskView
|
7
|
+
include Views
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@lines = []
|
12
|
+
@top = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def enter_task_view(course, chapter, unit)
|
16
|
+
@course = course
|
17
|
+
@chapter = chapter
|
18
|
+
@unit = unit
|
19
|
+
|
20
|
+
@test_result_lines = nil
|
21
|
+
@lines = @unit.task.markdown.lines
|
22
|
+
@top_bar_height = 4
|
23
|
+
@head_height = 2
|
24
|
+
|
25
|
+
initialize_window(@lines.count + @top_bar_height + @head_height)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_head(window)
|
31
|
+
window.setpos(0, 1)
|
32
|
+
window.clear_line
|
33
|
+
window.emphasize @course.title
|
34
|
+
window.write ' > '
|
35
|
+
window.emphasize @chapter.title
|
36
|
+
window.write ' > '
|
37
|
+
window.emphasize @unit.title
|
38
|
+
window.write ':'
|
39
|
+
|
40
|
+
window.setpos(1, 1)
|
41
|
+
window.clear_line
|
42
|
+
end
|
43
|
+
|
44
|
+
def show(window)
|
45
|
+
window.scrollok(true)
|
46
|
+
draw(window)
|
47
|
+
interact_with(window)
|
48
|
+
end
|
49
|
+
|
50
|
+
def draw(window)
|
51
|
+
@top = 0
|
52
|
+
window.attrset(A_NORMAL)
|
53
|
+
set_head(window)
|
54
|
+
|
55
|
+
@lines.each_with_index do |line, index|
|
56
|
+
window.setpos(index + 2, 1)
|
57
|
+
print_line(window, line, index)
|
58
|
+
end
|
59
|
+
|
60
|
+
window.setpos(0, 1)
|
61
|
+
window.refresh
|
62
|
+
end
|
63
|
+
|
64
|
+
def scroll_up(window)
|
65
|
+
if @top > 0
|
66
|
+
window.scrl(-1)
|
67
|
+
set_head(window)
|
68
|
+
|
69
|
+
@top -= 1
|
70
|
+
line = @lines[@top]
|
71
|
+
|
72
|
+
if line
|
73
|
+
window.setpos(2, 1)
|
74
|
+
print_line(window, line, @top)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def scroll_down(window)
|
80
|
+
if @top + Curses.lines <= @lines.count + @top_bar_height + @head_height
|
81
|
+
window.scrl(1)
|
82
|
+
set_head(window)
|
83
|
+
|
84
|
+
@top += 1
|
85
|
+
line = @lines[@top + window.maxy - 1]
|
86
|
+
window.setpos(window.maxy - 1, 1)
|
87
|
+
|
88
|
+
if line
|
89
|
+
print_line(window, line, @top)
|
90
|
+
else
|
91
|
+
window.clear_line
|
92
|
+
end
|
93
|
+
|
94
|
+
return true
|
95
|
+
else
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def print_line(window, line, index)
|
101
|
+
if @test_result_lines && index.between?(0, @test_result_lines.count + 1)
|
102
|
+
if @unit.mastered?
|
103
|
+
window.green(line, A_STANDOUT, full_line: true)
|
104
|
+
else
|
105
|
+
example_index = ((index + 1) / 4.0).ceil - 1
|
106
|
+
|
107
|
+
if @examples[example_index].passed?
|
108
|
+
window.green(line, A_STANDOUT, full_line: true)
|
109
|
+
else
|
110
|
+
window.red(line, A_STANDOUT, full_line: true)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
window.print_markdown(line.strip)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def interact_with(window)
|
119
|
+
while char = window.getch
|
120
|
+
scrollable = true
|
121
|
+
|
122
|
+
case char
|
123
|
+
when 'v' # Verifiy
|
124
|
+
print_test_results(window)
|
125
|
+
return
|
126
|
+
when 'c' # clear
|
127
|
+
reset_screen(window)
|
128
|
+
return
|
129
|
+
when 'o' # open solution file
|
130
|
+
open_editor(@unit.solution.path)
|
131
|
+
when Curses::KEY_DOWN, Curses::KEY_CTRL_N
|
132
|
+
scrollable = scroll_down(window)
|
133
|
+
when Curses::KEY_UP, Curses::KEY_CTRL_P
|
134
|
+
scrollable = scroll_up(window)
|
135
|
+
when Curses::KEY_NPAGE, ?\s # white space
|
136
|
+
0.upto(window.maxy - 2) do |n|
|
137
|
+
scrolled = scroll_down(window)
|
138
|
+
|
139
|
+
unless scrolled
|
140
|
+
scrollable = false if n == 0
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
when Curses::KEY_PPAGE
|
145
|
+
0.upto(window.maxy - 2) do |n|
|
146
|
+
scrolled = scroll_up(window)
|
147
|
+
|
148
|
+
unless scrolled
|
149
|
+
scrollable = false if n == 0
|
150
|
+
break
|
151
|
+
end
|
152
|
+
end
|
153
|
+
when Curses::KEY_LEFT, Curses::KEY_CTRL_T
|
154
|
+
while scroll_up(window)
|
155
|
+
end
|
156
|
+
when Curses::KEY_RIGHT, Curses::KEY_CTRL_B
|
157
|
+
while scroll_down(window)
|
158
|
+
end
|
159
|
+
when Curses::KEY_BACKSPACE
|
160
|
+
broadcast(:reenter_units_menu, @course, @chapter, @unit)
|
161
|
+
return
|
162
|
+
when 27 # ESC
|
163
|
+
exit
|
164
|
+
end
|
165
|
+
|
166
|
+
Curses.beep unless scrollable
|
167
|
+
window.setpos(0, 1)
|
168
|
+
window.refresh
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def open_editor(file_path)
|
173
|
+
if OS.windows?
|
174
|
+
system "start '' '#{file_path}'"
|
175
|
+
elsif OS.mac?
|
176
|
+
system "open '#{file_path}'"
|
177
|
+
elsif OS.linux?
|
178
|
+
system "xdg-open '#{file_path}'"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def print_test_results(window)
|
183
|
+
result = @unit.solution.verify!
|
184
|
+
|
185
|
+
@test_result_lines = result.summary.strip.split("\n")
|
186
|
+
@lines = [''] + @test_result_lines + ['', ''] + @unit.task.markdown.lines
|
187
|
+
@examples = result.examples
|
188
|
+
|
189
|
+
initialize_window(@lines.count + @top_bar_height + @head_height)
|
190
|
+
end
|
191
|
+
|
192
|
+
def reset_screen(window)
|
193
|
+
@test_result_lines = nil
|
194
|
+
@lines = @unit.task.markdown.lines
|
195
|
+
height = [@lines.count + @top_bar_height + @head_height, Curses.lines].max
|
196
|
+
initialize_window(height)
|
197
|
+
end
|
198
|
+
|
199
|
+
def initialize_window(height)
|
200
|
+
@window = default_window(height)
|
201
|
+
main_panel(@window) { |window| show sub_window_below_top_bar(window) }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class TopBar
|
5
|
+
include Curses
|
6
|
+
|
7
|
+
attr_reader :height, :width, :panel
|
8
|
+
|
9
|
+
def initialize(window)
|
10
|
+
@height = 4
|
11
|
+
@width = window.maxx
|
12
|
+
@panel = create_panel(window, @width, @height)
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
@panel.refresh
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def create_panel(window, width, heigth)
|
22
|
+
panel = window.subwin(heigth, window.maxx, 0, 0)
|
23
|
+
|
24
|
+
panel.setpos(1, 1)
|
25
|
+
panel.write 'Use '
|
26
|
+
panel.emphasize 'UP KEY'
|
27
|
+
panel.write ' and '
|
28
|
+
panel.emphasize 'DOWN KEY'
|
29
|
+
panel.write ' for menu navigation'
|
30
|
+
panel.write ' | Enter menu with '
|
31
|
+
panel.emphasize 'RETURN'
|
32
|
+
panel.write ' | Go back with '
|
33
|
+
panel.emphasize 'BACKSPACE'
|
34
|
+
panel.write ' | Exit with '
|
35
|
+
panel.emphasize 'ESC'
|
36
|
+
panel.setpos(2, 1)
|
37
|
+
panel.write '_' * (window.maxx - 3)
|
38
|
+
|
39
|
+
panel
|
40
|
+
end
|
41
|
+
|
42
|
+
def emphasized(panel, text)
|
43
|
+
panel.write(text, Window::COLOR_2)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
|
5
|
+
class UnitsMenu
|
6
|
+
include Views
|
7
|
+
include Wisper::Publisher
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@position = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def enter_units_menu(course, chapter)
|
14
|
+
@window = default_window
|
15
|
+
@course = course
|
16
|
+
@chapter = chapter
|
17
|
+
|
18
|
+
main_panel(@window) do |window|
|
19
|
+
show sub_window_below_top_bar(window)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def reenter_units_menu(course, chapter, unit)
|
24
|
+
@course = course
|
25
|
+
@chapter = chapter
|
26
|
+
|
27
|
+
@position = chapter.units.find_index(unit)
|
28
|
+
enter_units_menu(@course, @chapter)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def show(window)
|
34
|
+
draw(window, @position)
|
35
|
+
interact_with(window)
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw(window, active_index = 0)
|
39
|
+
window.attrset(A_NORMAL)
|
40
|
+
window.setpos(0, 1)
|
41
|
+
window.emphasize @course.title
|
42
|
+
window.write ' > '
|
43
|
+
window.emphasize @chapter.title
|
44
|
+
window.write ' > - available units:'
|
45
|
+
|
46
|
+
menu_items.each_with_index do |item, index|
|
47
|
+
window.setpos(index + 2, 1)
|
48
|
+
window.print_indicator(units[index])
|
49
|
+
window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
|
50
|
+
window.write " #{item.to_s} "
|
51
|
+
end
|
52
|
+
|
53
|
+
window.refresh
|
54
|
+
end
|
55
|
+
|
56
|
+
def interact_with(window)
|
57
|
+
while char = window.getch
|
58
|
+
case char
|
59
|
+
when KEY_UP
|
60
|
+
@position -= 1
|
61
|
+
when KEY_DOWN
|
62
|
+
@position += 1
|
63
|
+
when 10 # Enter
|
64
|
+
broadcast(:enter_task_view, @course, @chapter, units[@position])
|
65
|
+
return
|
66
|
+
when 263 # Backspace
|
67
|
+
broadcast(:reenter_chapters_menu, @course, @chapter)
|
68
|
+
return
|
69
|
+
when 27 # ESC
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
@position = menu_items.length - 1 if @position < 0
|
74
|
+
@position = 0 if @position >= menu_items.length
|
75
|
+
draw(window, @position)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def units
|
80
|
+
@chapter.units
|
81
|
+
end
|
82
|
+
|
83
|
+
def menu_items
|
84
|
+
@menu_items = units.map do |unit|
|
85
|
+
unit.title
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|