terminal_calendar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/LICENSE +7 -0
- data/README.md +63 -0
- data/Rakefile +12 -0
- data/_doc/cal-screenshot.png +0 -0
- data/_doc/date-picker.gif +0 -0
- data/certs/mcordell.pem +26 -0
- data/lib/date_extensions.rb +39 -0
- data/lib/terminal_calendar/date_picker.rb +160 -0
- data/lib/terminal_calendar/month/calendar_day.rb +69 -0
- data/lib/terminal_calendar/month.rb +98 -0
- data/lib/terminal_calendar/selection/cell.rb +74 -0
- data/lib/terminal_calendar/selection/grid.rb +210 -0
- data/lib/terminal_calendar/selection/month_page.rb +68 -0
- data/lib/terminal_calendar/selection/month_year_dialog.rb +99 -0
- data/lib/terminal_calendar/selection/null_cell.rb +32 -0
- data/lib/terminal_calendar/selection/selector.rb +167 -0
- data/lib/terminal_calendar/version.rb +5 -0
- data/lib/terminal_calendar.rb +40 -0
- data/lib/tty/prompt/carousel.rb +115 -0
- data/sig/terminal_calendar.rbs +4 -0
- data/terminal_calendar.gemspec +42 -0
- data.tar.gz.sig +0 -0
- metadata +176 -0
- metadata.gz.sig +2 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rubocop:disable Naming::MethodParameterName
|
3
|
+
class TerminalCalendar
|
4
|
+
module Selection
|
5
|
+
class Grid
|
6
|
+
# @return [Array<Array>]
|
7
|
+
# @api private
|
8
|
+
attr_reader :grid
|
9
|
+
|
10
|
+
attr_reader :highlighted_position
|
11
|
+
|
12
|
+
# @return [Integer]
|
13
|
+
# @api private
|
14
|
+
attr_accessor :redraw_at
|
15
|
+
|
16
|
+
# Builds a new grid from a array of arrays of objects
|
17
|
+
#
|
18
|
+
# @param [Array<Array>] objects The objects to build the grid from.
|
19
|
+
# @param [Hash] opts
|
20
|
+
#
|
21
|
+
# @return [Grid]
|
22
|
+
# @api public
|
23
|
+
def self.build_from_objects(objects, _opts={})
|
24
|
+
new(objects.first.length, objects.length).tap do |new_grid|
|
25
|
+
new_grid.populate_from_objects(objects)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Initializes a new grid with the specified width and height.
|
30
|
+
|
31
|
+
# @param width [Integer] the width of the grid
|
32
|
+
# @param height [Integer] the height of the grid
|
33
|
+
# @param pastel [Pastel] The pastel object to used for decorating text
|
34
|
+
def initialize(width, height, pastel: Pastel.new)
|
35
|
+
@grid = Array.new(height) do
|
36
|
+
Array.new(width) { NullCell.new }
|
37
|
+
end
|
38
|
+
@pastel = pastel
|
39
|
+
end
|
40
|
+
|
41
|
+
# Builds a the grid from an array of arrays of objects
|
42
|
+
#
|
43
|
+
# @param [Array<Array>] objects the objects to populate the grid with
|
44
|
+
# @return [Grid] self
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def populate_from_objects(objects)
|
48
|
+
objects.each_with_index do |object_row, y|
|
49
|
+
object_row.each_with_index do |obj, x|
|
50
|
+
populate_position(x, y, obj)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Populates a position on the grid with the given object.
|
57
|
+
|
58
|
+
# @param x [Integer] the x-coordinate of the position
|
59
|
+
# @param y [Integer] the y-coordinate of the position
|
60
|
+
# @param object [Object] the object to wrap in the cell
|
61
|
+
# @return [Cell] the created cell
|
62
|
+
def populate_position(x, y, object)
|
63
|
+
return grid[y][x] if object.null?
|
64
|
+
|
65
|
+
grid[y][x] = Cell.new(object)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the cell at the given coordinates.
|
69
|
+
#
|
70
|
+
# @param [Integer] x the x coordinate
|
71
|
+
# @param [Integer] y the y coordinate
|
72
|
+
# @return [Cell] the cell at the given coordinates
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def cell(x, y)
|
76
|
+
grid[y][x]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Renders specified number of lines from the bottom of the grid as printable strings
|
80
|
+
# @param count [Integer, Symbol] The number of lines to render. If set to :all, all lines will be rendered.
|
81
|
+
# @return [Array<String>] An array of strings representing the rendered lines.
|
82
|
+
def render_lines(count=:all)
|
83
|
+
start_at = render_start(count)
|
84
|
+
|
85
|
+
(start_at..bottom_of_grid).map do |i|
|
86
|
+
render_row(i)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the y value of the bottom of the grid
|
91
|
+
#
|
92
|
+
# @return [Integer] the bottom of the grid
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def bottom_of_grid
|
96
|
+
grid.length - 1
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the y value of the top of the grid
|
100
|
+
#
|
101
|
+
# @return [Integer] the top of the grid
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def top_of_grid
|
105
|
+
0
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the bottom row of the grid
|
109
|
+
#
|
110
|
+
# @return [Array<TerminalCalendar::Selection::Cell>]
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def bottom_row
|
114
|
+
grid[bottom_of_grid]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the top row of the grid
|
118
|
+
#
|
119
|
+
# @return [Array<TerminalCalendar::Selection::Cell>]
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def top_row
|
123
|
+
grid[top_of_grid]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the first cell in the grid that is not null working from right to left, bottom to top.
|
127
|
+
#
|
128
|
+
# @return [Array<Integer>] the bottom right live cell position in the grid format x,y
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# bottom_right_live_cell_position #=> [3, 3]
|
132
|
+
#
|
133
|
+
# @api public
|
134
|
+
def bottom_right_live_cell_position
|
135
|
+
bottom_of_grid.downto(top_of_grid).each do |y|
|
136
|
+
row = grid[y]
|
137
|
+
(row.length - 1).downto(0).each do |x|
|
138
|
+
return [x, y] unless row[x].null?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the first cell in the grid that is not null working from left to right, top to bottom.
|
144
|
+
#
|
145
|
+
# @return [Array<Integer>] the top left live cell position in the grid format x, y
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# top_left_live_cell_position #=> [2, 0]
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
def top_left_live_cell_position
|
152
|
+
(top_of_grid..bottom_of_grid).each do |y|
|
153
|
+
grid[y].each_with_index do |cell, x|
|
154
|
+
return [x, y] unless cell.null?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns all selected cells in the grid
|
160
|
+
# @return [Array<Cell>] Selected cells
|
161
|
+
def selected_cells
|
162
|
+
grid.flatten.select(&:selected?)
|
163
|
+
end
|
164
|
+
|
165
|
+
def redraw_lines
|
166
|
+
render_lines(redraw_at.nil? || redraw_at <= 0 ? :all : (grid.length - redraw_at))
|
167
|
+
end
|
168
|
+
|
169
|
+
def row_end
|
170
|
+
grid.first.length - 1
|
171
|
+
end
|
172
|
+
|
173
|
+
def highlighted_position=(pos)
|
174
|
+
@highlighted_position = pos
|
175
|
+
end
|
176
|
+
|
177
|
+
def clear_highlight!
|
178
|
+
@highlighted_position = nil
|
179
|
+
end
|
180
|
+
|
181
|
+
def highlighted?
|
182
|
+
@highlighted_position.nil?
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# @api private
|
188
|
+
def render_row(i)
|
189
|
+
row = grid[i]
|
190
|
+
highlighted_x, highlighted_y = highlighted_position
|
191
|
+
return row.map(&:render).join(' ') unless highlighted_y == i
|
192
|
+
|
193
|
+
(0..row_end).map do |x|
|
194
|
+
rendered = row[x].render
|
195
|
+
next rendered unless x == highlighted_x
|
196
|
+
|
197
|
+
@pastel.inverse(rendered)
|
198
|
+
end.join(' ')
|
199
|
+
end
|
200
|
+
|
201
|
+
# @api private
|
202
|
+
def render_start(count)
|
203
|
+
return 0 if count == :all || count > bottom_of_grid
|
204
|
+
|
205
|
+
bottom_of_grid - count
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
# rubocop:enable Naming::MethodParameterName
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class TerminalCalendar
|
3
|
+
module Selection
|
4
|
+
class MonthPage
|
5
|
+
WEEK_ROW = %w(Su Mo Tu We Th Fr Sa).freeze
|
6
|
+
|
7
|
+
attr_reader :selection_grid, :month
|
8
|
+
|
9
|
+
def self.build(month)
|
10
|
+
new(month)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(month, pastel=Pastel.new)
|
14
|
+
@selection_grid = Grid.build_from_objects(month.as_rows)
|
15
|
+
@month = month
|
16
|
+
@pastel = pastel
|
17
|
+
end
|
18
|
+
|
19
|
+
# Renders the calendar as a string.
|
20
|
+
# @return [String] the rendered calendar as a string.
|
21
|
+
def render
|
22
|
+
render_rows.join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def redraw_lines
|
26
|
+
if selection_grid.redraw_at.nil? || selection_grid.redraw_at.negative?
|
27
|
+
calendar_header(selected: selection_grid&.redraw_at == -1).concat(selection_grid.redraw_lines)
|
28
|
+
else
|
29
|
+
selection_grid.redraw_lines
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def line_count
|
34
|
+
@line_count ||= render_rows.count
|
35
|
+
end
|
36
|
+
|
37
|
+
def selection_grid_lines
|
38
|
+
selection_grid.render_lines
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def render_rows
|
44
|
+
calendar_header.concat(selection_grid_lines)
|
45
|
+
end
|
46
|
+
|
47
|
+
def refresh(lines)
|
48
|
+
TTY::Cursor.clear_lines(lines)
|
49
|
+
end
|
50
|
+
|
51
|
+
def calendar_header(selected: false)
|
52
|
+
week_row = WEEK_ROW.join(' ')
|
53
|
+
month_row = month_header
|
54
|
+
pad_size = (week_row.length - month_row.length) / 2
|
55
|
+
month_row = @pastel.inverse(month_row) if selected
|
56
|
+
month_row = (' ' * pad_size).concat(month_row)
|
57
|
+
[
|
58
|
+
month_row,
|
59
|
+
week_row
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
def month_header
|
64
|
+
Date::MONTHNAMES[@month.month] + " #{@month.year}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class TerminalCalendar
|
3
|
+
module Selection
|
4
|
+
class MonthYearDialog
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
# Initializes a new MonthYearDialog instance.
|
8
|
+
#
|
9
|
+
# @param start_at [Date] the starting date for the dialog (default: Date.today)
|
10
|
+
# @param input [IO] the input stream to read user input from (default: $stdin)
|
11
|
+
# @param output [IO] the output stream to write messages to (default: $stdout)
|
12
|
+
# @param env [Hash] the environment variables (default: ENV)
|
13
|
+
# @param interrupt [Symbol] the behavior when an interrupt signal is received (:error or :exit) (default: :error)
|
14
|
+
# @param track_history [Boolean] whether to track user input history (default: true)
|
15
|
+
# @return [MonthYearDialog] the initialized MonthYearDialog instance
|
16
|
+
def initialize(input: $stdin, output: $stdout, env: ENV, interrupt: :error, track_history: true,
|
17
|
+
start_at: Date.today)
|
18
|
+
@output = output
|
19
|
+
@reader = TTY::Reader.new(
|
20
|
+
input: input,
|
21
|
+
output: output,
|
22
|
+
interrupt: interrupt,
|
23
|
+
track_history: track_history,
|
24
|
+
env: env
|
25
|
+
)
|
26
|
+
initialize_carousels(start_at)
|
27
|
+
@cursor = TTY::Cursor
|
28
|
+
end
|
29
|
+
|
30
|
+
# Renders the dialog for month and year to the output
|
31
|
+
def render
|
32
|
+
month_car.render
|
33
|
+
@output.puts
|
34
|
+
year_car.render
|
35
|
+
end
|
36
|
+
|
37
|
+
def select
|
38
|
+
cursor.invisible do
|
39
|
+
render
|
40
|
+
|
41
|
+
key_capture
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def redraw(amt=2)
|
46
|
+
@output.print(cursor.clear_lines(amt))
|
47
|
+
amt == 2 ? render : year_car.render
|
48
|
+
end
|
49
|
+
|
50
|
+
def_delegator :@month_car, :selected_option, :selected_month
|
51
|
+
def_delegator :@year_car, :selected_option, :selected_year
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def key_capture
|
56
|
+
loop do
|
57
|
+
case get_key_press
|
58
|
+
when :up, :down
|
59
|
+
toggle_selected
|
60
|
+
redraw(2)
|
61
|
+
when :left
|
62
|
+
selected_car.move_left
|
63
|
+
redraw(@selected == :year ? 1 : 2)
|
64
|
+
when :right
|
65
|
+
selected_car.move_right
|
66
|
+
redraw(@selected == :year ? 1 : 2)
|
67
|
+
when :return
|
68
|
+
return Date.new(selected_year.to_i, Date::MONTHNAMES.find_index(selected_month), 1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_key_press
|
74
|
+
press = reader.read_keypress
|
75
|
+
TTY::Reader::Keys.keys.fetch(press) { press }
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize_carousels(start_at)
|
79
|
+
@selected = :month
|
80
|
+
@month_car = TTY::Prompt::Carousel.new(Date::MONTHNAMES.compact, start_at: start_at.month - 1,
|
81
|
+
option_style: :inverse, output: output)
|
82
|
+
@year_car = TTY::Prompt::Carousel.new((0..3000).map(&:to_s), start_at: start_at.year, output: output,
|
83
|
+
margin: 0, padding: 4)
|
84
|
+
end
|
85
|
+
|
86
|
+
def selected_car
|
87
|
+
@selected == :month ? month_car : year_car
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader(:output, :month_car, :year_car, :cursor, :reader)
|
91
|
+
|
92
|
+
def toggle_selected
|
93
|
+
@selected = @selected == :month ? :year : :month
|
94
|
+
month_car.option_style = @selected == :month ? :inverse : nil
|
95
|
+
year_car.option_style = @selected == :year ? :inverse : nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
class TerminalCalendar
|
5
|
+
module Selection
|
6
|
+
class NullCell < Cell
|
7
|
+
def initialize
|
8
|
+
super(nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
' '
|
13
|
+
end
|
14
|
+
|
15
|
+
# Checks if the object is null.
|
16
|
+
#
|
17
|
+
# @return [true] Returns true.
|
18
|
+
def null?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [false] Returns false.
|
23
|
+
def selected
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def toggle_selected!
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rubocop:disable Naming::MethodParameterName
|
3
|
+
class TerminalCalendar
|
4
|
+
module Selection
|
5
|
+
class Selector
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
DIRECTIONS = %i(up down left right).freeze
|
9
|
+
|
10
|
+
attr_reader(:x, :y, :selection_grid)
|
11
|
+
|
12
|
+
def_delegator :@page, :selection_grid
|
13
|
+
def_delegators :selection_grid, :bottom_of_grid, :top_of_grid, :row_end
|
14
|
+
|
15
|
+
def self.build(page, initial_spot)
|
16
|
+
if initial_spot == :bottom
|
17
|
+
x, y = page.selection_grid.bottom_right_live_cell_position
|
18
|
+
else
|
19
|
+
x, y = page.selection_grid.top_left_live_cell_position
|
20
|
+
end
|
21
|
+
|
22
|
+
new(x, y, page)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initializes a new selector
|
26
|
+
#
|
27
|
+
# @param x [Integer] the x-coordinate of the selector
|
28
|
+
# @param y [Integer] the y-coordinate of the selector
|
29
|
+
# @param selection_grid [Array<Array<TerminalCalendar::Selection::Cell>>] the selection grid
|
30
|
+
# @param wrap [Symbol] (optional) the wrap direction, defaults to :all
|
31
|
+
def initialize(x, y, page, wrap: [])
|
32
|
+
@x = x
|
33
|
+
@page = page
|
34
|
+
@top_of_grid = 0
|
35
|
+
@y = y
|
36
|
+
@wrap_directions = wrap == :all ? DIRECTIONS : wrap
|
37
|
+
post_move
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_header?
|
41
|
+
y == -1
|
42
|
+
end
|
43
|
+
|
44
|
+
# Toggles the selected state of the cell at the current position on the grid.
|
45
|
+
#
|
46
|
+
# @return [void]
|
47
|
+
def toggle_selected!
|
48
|
+
return unless on_grid?
|
49
|
+
|
50
|
+
selection_grid.cell(@x, @y).toggle_selected!
|
51
|
+
end
|
52
|
+
|
53
|
+
# Determines if the selector is within the selection grid.
|
54
|
+
#
|
55
|
+
# @return [Boolean] Returns true if the point is within the selection grid, false otherwise.
|
56
|
+
def on_grid?
|
57
|
+
x >= leftmost_gridsquare && x <= selection_grid.row_end &&
|
58
|
+
y >= selection_grid.top_of_grid && y <= selection_grid.bottom_of_grid
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the leftmost grid square.
|
62
|
+
#
|
63
|
+
# @return [Integer] the leftmost grid square
|
64
|
+
def leftmost_gridsquare
|
65
|
+
0
|
66
|
+
end
|
67
|
+
|
68
|
+
# Moves the selector in the specified direction.
|
69
|
+
#
|
70
|
+
# @param direction [Symbol] The direction to move in.
|
71
|
+
# Valid directions are: :up, :down, :left, :right.
|
72
|
+
# @raise [ArgumentError] if the specified direction is not valid.
|
73
|
+
# @return [void]
|
74
|
+
def move(direction)
|
75
|
+
fail ArgumentError.new("Unknown direction #{direction}") unless DIRECTIONS.include?(direction)
|
76
|
+
|
77
|
+
pre_move
|
78
|
+
|
79
|
+
result = send("move_#{direction}")
|
80
|
+
|
81
|
+
post_move
|
82
|
+
|
83
|
+
result
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
attr_writer(:x, :y)
|
89
|
+
|
90
|
+
def pre_move
|
91
|
+
selection_grid.redraw_at = nil
|
92
|
+
selection_grid.clear_highlight!
|
93
|
+
end
|
94
|
+
|
95
|
+
def post_move
|
96
|
+
unless selection_grid.redraw_at
|
97
|
+
selection_grid.redraw_at = y
|
98
|
+
end
|
99
|
+
return unless on_grid?
|
100
|
+
|
101
|
+
selection_grid.highlighted_position = [x, y]
|
102
|
+
end
|
103
|
+
|
104
|
+
def wrap(direction)
|
105
|
+
case direction
|
106
|
+
when :up
|
107
|
+
selection_grid.redraw_at = 0
|
108
|
+
self.y = bottom_of_grid
|
109
|
+
when :down
|
110
|
+
self.y = top_of_grid
|
111
|
+
when :left
|
112
|
+
self.x = row_end
|
113
|
+
when :right
|
114
|
+
self.x = leftmost_gridsquare
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def move_up
|
119
|
+
if y == top_of_grid
|
120
|
+
wrap(:up) if @wrap_directions.include? :up
|
121
|
+
self.y -= 1
|
122
|
+
elsif on_header?
|
123
|
+
selection_grid.redraw_at = -2
|
124
|
+
self.y = bottom_of_grid
|
125
|
+
else
|
126
|
+
self.y -= 1
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def move_down
|
131
|
+
if y == bottom_of_grid
|
132
|
+
wrap(:down)
|
133
|
+
elsif on_header?
|
134
|
+
selection_grid.redraw_at = y == -1 ? -2 : y
|
135
|
+
self.y += 1
|
136
|
+
else
|
137
|
+
self.y += 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def move_left
|
142
|
+
if x == leftmost_gridsquare
|
143
|
+
return wrap(:left) if @wrap_directions.include? :left
|
144
|
+
|
145
|
+
:off_left
|
146
|
+
else
|
147
|
+
self.x -= 1
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def move_right
|
152
|
+
if x == row_end
|
153
|
+
return wrap(:right) if @wrap_directions.include? :right
|
154
|
+
|
155
|
+
:off_right
|
156
|
+
else
|
157
|
+
self.x += 1
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def redraw_header!
|
162
|
+
selection_grid.redraw_at = -2
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
# rubocop:enable Naming::MethodParameterName
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require_relative 'tty/prompt/carousel'
|
5
|
+
require_relative 'terminal_calendar/version'
|
6
|
+
require_relative 'terminal_calendar/month'
|
7
|
+
require_relative 'terminal_calendar/selection/cell'
|
8
|
+
require_relative 'terminal_calendar/selection/null_cell'
|
9
|
+
require_relative 'terminal_calendar/selection/selector'
|
10
|
+
require_relative 'terminal_calendar/selection/grid'
|
11
|
+
require_relative 'terminal_calendar/selection/month_page'
|
12
|
+
require_relative 'terminal_calendar/selection/month_year_dialog'
|
13
|
+
require_relative 'terminal_calendar/date_picker'
|
14
|
+
require_relative 'date_extensions'
|
15
|
+
require 'pastel'
|
16
|
+
require 'tty-cursor'
|
17
|
+
require 'tty-reader'
|
18
|
+
|
19
|
+
class TerminalCalendar
|
20
|
+
class Error < StandardError; end
|
21
|
+
|
22
|
+
# This method allows the user to select one or more days starting from the current month calendar.
|
23
|
+
#
|
24
|
+
# @return [Array<Date>] The selected days.
|
25
|
+
def self.date_picker
|
26
|
+
TerminalCalendar::DatePicker.pick
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param month [Month] month to render, defaults to the current month
|
30
|
+
# @return [String] the month page as a string
|
31
|
+
def self.cal(month=Month.this_month())
|
32
|
+
Selection::MonthPage.build(month).render
|
33
|
+
end
|
34
|
+
|
35
|
+
# Stores a cache of month objects
|
36
|
+
# @api private
|
37
|
+
def self.all_months
|
38
|
+
@all_months ||= {}
|
39
|
+
end
|
40
|
+
end
|