charming 0.2.1 → 0.2.3
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 +4 -4
- data/README.md +16 -3
- data/lib/charming/application.rb +48 -0
- data/lib/charming/cli.rb +16 -4
- data/lib/charming/controller/key_dispatch.rb +113 -0
- data/lib/charming/controller/session_state.rb +11 -0
- data/lib/charming/controller/terminal.rb +33 -0
- data/lib/charming/controller.rb +13 -40
- data/lib/charming/escape.rb +81 -0
- data/lib/charming/events/mouse_event.rb +22 -9
- data/lib/charming/generators/app_file_generator.rb +29 -0
- data/lib/charming/generators/app_generator.rb +13 -25
- data/lib/charming/generators/base.rb +5 -0
- data/lib/charming/generators/layout_generator.rb +155 -0
- data/lib/charming/generators/screen_generator.rb +0 -29
- data/lib/charming/generators/templates/app/Gemfile.template +6 -0
- data/lib/charming/generators/templates/app/README.md.template +15 -1
- data/lib/charming/generators/templates/app/application.template +0 -6
- data/lib/charming/generators/templates/app/application_controller.template +0 -10
- data/lib/charming/generators/templates/app/dot_rspec.template +2 -0
- data/lib/charming/generators/templates/app/layout.template +4 -93
- data/lib/charming/generators/templates/app/routes.template +3 -1
- data/lib/charming/generators/templates/layout/sidebar/application_layout.rb.template +110 -0
- data/lib/charming/image/protocol/kitty.rb +133 -0
- data/lib/charming/image/protocol.rb +18 -0
- data/lib/charming/image/source.rb +95 -0
- data/lib/charming/image/terminal.rb +52 -0
- data/lib/charming/image/transmit.rb +11 -0
- data/lib/charming/image.rb +21 -0
- data/lib/charming/internal/event_loop.rb +155 -0
- data/lib/charming/internal/terminal/adapter.rb +14 -0
- data/lib/charming/internal/terminal/key_normalizer.rb +20 -0
- data/lib/charming/internal/terminal/memory_backend.rb +21 -3
- data/lib/charming/internal/terminal/modified_key_parser.rb +63 -0
- data/lib/charming/internal/terminal/mouse_parser.rb +32 -27
- data/lib/charming/internal/terminal/tty_backend.rb +79 -2
- data/lib/charming/presentation/components/activity_indicator.rb +2 -19
- data/lib/charming/presentation/components/autocomplete.rb +7 -0
- data/lib/charming/presentation/components/chart.rb +80 -0
- data/lib/charming/presentation/components/error_screen.rb +1 -1
- data/lib/charming/presentation/components/filepicker.rb +101 -0
- data/lib/charming/presentation/components/form/builder.rb +5 -0
- data/lib/charming/presentation/components/form/field.rb +5 -0
- data/lib/charming/presentation/components/form/input.rb +14 -4
- data/lib/charming/presentation/components/form/multiselect.rb +105 -0
- data/lib/charming/presentation/components/form/textarea.rb +18 -8
- data/lib/charming/presentation/components/form.rb +6 -0
- data/lib/charming/presentation/components/image.rb +38 -0
- data/lib/charming/presentation/components/list.rb +22 -4
- data/lib/charming/presentation/components/modal.rb +4 -4
- data/lib/charming/presentation/components/paginator.rb +54 -0
- data/lib/charming/presentation/components/progressbar.rb +26 -4
- data/lib/charming/presentation/components/sparkline.rb +38 -0
- data/lib/charming/presentation/components/spinner.rb +22 -3
- data/lib/charming/presentation/components/stopwatch.rb +55 -0
- data/lib/charming/presentation/components/table.rb +42 -1
- data/lib/charming/presentation/components/text_area.rb +3 -4
- data/lib/charming/presentation/components/text_input.rb +11 -6
- data/lib/charming/presentation/components/time_display.rb +20 -0
- data/lib/charming/presentation/components/timer.rb +43 -0
- data/lib/charming/presentation/components/toast.rb +4 -3
- data/lib/charming/presentation/components/viewport/content_lines.rb +1 -1
- data/lib/charming/presentation/components/viewport/line_window.rb +1 -1
- data/lib/charming/presentation/components/viewport.rb +11 -1
- data/lib/charming/presentation/markdown/renderer.rb +1 -1
- data/lib/charming/presentation/markdown/style_config.rb +1 -0
- data/lib/charming/presentation/markdown/table_renderer.rb +2 -3
- data/lib/charming/presentation/ui/adaptive_color.rb +20 -0
- data/lib/charming/presentation/ui/ansi_codes.rb +5 -2
- data/lib/charming/presentation/ui/background.rb +58 -0
- data/lib/charming/presentation/ui/border.rb +14 -1
- data/lib/charming/presentation/ui/border_painter.rb +23 -11
- data/lib/charming/presentation/ui/braille_canvas.rb +80 -0
- data/lib/charming/presentation/ui/canvas.rb +1 -0
- data/lib/charming/presentation/ui/gradient.rb +47 -0
- data/lib/charming/presentation/ui/style.rb +119 -19
- data/lib/charming/presentation/{markdown → ui}/text_wrapper.rb +4 -4
- data/lib/charming/presentation/ui/truncate.rb +29 -0
- data/lib/charming/presentation/ui/width.rb +11 -0
- data/lib/charming/presentation/ui.rb +52 -11
- data/lib/charming/presentation/view.rb +8 -6
- data/lib/charming/response.rb +11 -6
- data/lib/charming/runtime.rb +167 -90
- data/lib/charming/version.rb +1 -1
- data/lib/charming/welcome/controller.rb +23 -0
- data/lib/charming/welcome/show_view.rb +113 -0
- data/lib/charming/welcome.rb +13 -0
- metadata +34 -9
- data/lib/charming/generators/templates/app/home_controller.template +0 -6
- data/lib/charming/generators/templates/app/home_state.template +0 -7
- data/lib/charming/generators/templates/app/spec_controller.template +0 -17
- data/lib/charming/generators/templates/app/spec_state.template +0 -17
- data/lib/charming/generators/templates/app/spec_view.template +0 -16
- data/lib/charming/generators/templates/app/view.template +0 -21
|
@@ -13,6 +13,7 @@ module Charming
|
|
|
13
13
|
BASE_FILE_TEMPLATES = [
|
|
14
14
|
["Gemfile", "app/Gemfile.template", false],
|
|
15
15
|
["Rakefile", "app/Rakefile.template", false],
|
|
16
|
+
[".rspec", "app/dot_rspec.template", false],
|
|
16
17
|
["README.md", "app/README.md.template", false],
|
|
17
18
|
["%<name>s.gemspec", "app/gemspec.template", false],
|
|
18
19
|
["exe/%<name>s", "app/executable.template", true],
|
|
@@ -21,16 +22,10 @@ module Charming
|
|
|
21
22
|
["lib/%<name>s/version.rb", "app/version.template", false],
|
|
22
23
|
["config/routes.rb", "app/routes.template", false],
|
|
23
24
|
["app/state/application_state.rb", "app/application_state.template", false],
|
|
24
|
-
["app/state/home_state.rb", "app/home_state.template", false],
|
|
25
25
|
["app/controllers/application_controller.rb", "app/application_controller.template", false],
|
|
26
|
-
["app/controllers/home_controller.rb", "app/home_controller.template", false],
|
|
27
26
|
["app/views/layouts/application_layout.rb", "app/layout.template", false],
|
|
28
|
-
["app/views/home/show_view.rb", "app/view.template", false],
|
|
29
27
|
["app/components/.keep", "app/keep.template", false],
|
|
30
|
-
["spec/spec_helper.rb", "app/spec_helper.template", false]
|
|
31
|
-
["spec/state/home_state_spec.rb", "app/spec_state.template", false],
|
|
32
|
-
["spec/controllers/home_controller_spec.rb", "app/spec_controller.template", false],
|
|
33
|
-
["spec/views/home/show_view_spec.rb", "app/spec_view.template", false]
|
|
28
|
+
["spec/spec_helper.rb", "app/spec_helper.template", false]
|
|
34
29
|
].freeze
|
|
35
30
|
|
|
36
31
|
# The list of [relative-path, template-path, executable-flag] triples to render in
|
|
@@ -95,15 +90,23 @@ module Charming
|
|
|
95
90
|
app_class: name.class_name,
|
|
96
91
|
gemspec_attributes: gemspec_attributes,
|
|
97
92
|
gemspec_dependencies: gemspec_dependencies,
|
|
98
|
-
controller_actions: controller_actions,
|
|
99
|
-
controller_helpers: controller_helpers,
|
|
100
93
|
database_require: database_require,
|
|
101
94
|
model_loader: model_loader,
|
|
102
95
|
env_setup: env_setup,
|
|
103
|
-
database_spec_setup: database_spec_setup
|
|
96
|
+
database_spec_setup: database_spec_setup,
|
|
97
|
+
database_setup: database_setup
|
|
104
98
|
}
|
|
105
99
|
end
|
|
106
100
|
|
|
101
|
+
# README instructions for preparing the database before first run. Empty for
|
|
102
|
+
# non-database apps.
|
|
103
|
+
def database_setup
|
|
104
|
+
return "" unless database?
|
|
105
|
+
|
|
106
|
+
"Set up the database (create + load schema + seed):\n\n" \
|
|
107
|
+
"```sh\nbundle exec charming db:setup\n```\n\n"
|
|
108
|
+
end
|
|
109
|
+
|
|
107
110
|
# Pins CHARMING_ENV to "test" before the app (and its database config) loads, so
|
|
108
111
|
# specs hit db/test.sqlite3. Empty for non-database apps.
|
|
109
112
|
def env_setup
|
|
@@ -174,21 +177,6 @@ module Charming
|
|
|
174
177
|
" spec.add_dependency \"sqlite3\", \"~> 2.0\""
|
|
175
178
|
end
|
|
176
179
|
|
|
177
|
-
# The body of the home controller's `show` action.
|
|
178
|
-
def controller_actions
|
|
179
|
-
"\n def show\n" \
|
|
180
|
-
" render :show, home: home, palette: command_palette\n" \
|
|
181
|
-
" end"
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# The body of the home controller's private `home` helper, prefixed by a blank line.
|
|
185
|
-
def controller_helpers
|
|
186
|
-
"\n\n private\n" \
|
|
187
|
-
" def home\n" \
|
|
188
|
-
" state(:home, HomeState)\n" \
|
|
189
|
-
" end"
|
|
190
|
-
end
|
|
191
|
-
|
|
192
180
|
# The `require_relative "../config/database"` line when the app is database-configured.
|
|
193
181
|
def database_require
|
|
194
182
|
database? ? "require_relative \"../config/database\"" : ""
|
|
@@ -22,6 +22,11 @@ module Charming
|
|
|
22
22
|
# Status output stream and destination directory accessor (subclasses use these).
|
|
23
23
|
attr_reader :out, :destination
|
|
24
24
|
|
|
25
|
+
# True when overwriting existing files was requested (`--force`).
|
|
26
|
+
def force?
|
|
27
|
+
@force
|
|
28
|
+
end
|
|
29
|
+
|
|
25
30
|
# Writes *content* to *path* (relative to the destination), creating intermediate
|
|
26
31
|
# directories as needed. Raises Generators::Error when the file already exists and
|
|
27
32
|
# *force* is false. Marks the file as executable when *executable:* is true.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Charming
|
|
4
|
+
module Generators
|
|
5
|
+
# LayoutGenerator implements `charming generate layout [--style sidebar]`. It restores
|
|
6
|
+
# the full app chrome that `charming new` no longer ships by default: the sidebar
|
|
7
|
+
# layout, the sidebar/content focus ring, the command palette bindings, and the
|
|
8
|
+
# built-in theme registration. Safe to re-run: existing chrome is left untouched.
|
|
9
|
+
class LayoutGenerator < AppFileGenerator
|
|
10
|
+
STYLES = %w[sidebar].freeze
|
|
11
|
+
USAGE = "Usage: charming generate layout [--style sidebar]"
|
|
12
|
+
|
|
13
|
+
# *name* is unused (the CLI passes "application"); *args* may carry `--style STYLE`.
|
|
14
|
+
def initialize(name, args, out:, destination:, force: false)
|
|
15
|
+
@style = extract_style(args)
|
|
16
|
+
super
|
|
17
|
+
raise Error, USAGE if args.any?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Writes the styled application layout, then inserts the controller chrome and the
|
|
21
|
+
# theme registration into the existing app files.
|
|
22
|
+
def generate
|
|
23
|
+
write_layout
|
|
24
|
+
insert_controller_chrome
|
|
25
|
+
insert_theme_registration
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# The layout style name (currently always "sidebar").
|
|
31
|
+
attr_reader :style
|
|
32
|
+
|
|
33
|
+
# The file-name suffix used by `app_path` (only used by the parent class).
|
|
34
|
+
def suffix
|
|
35
|
+
"layout"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Extracts the optional `--style` argument from *args*, removing it in place.
|
|
39
|
+
# Defaults to the first supported style when absent.
|
|
40
|
+
def extract_style(args)
|
|
41
|
+
inline = args.find { |arg| arg.start_with?("--style=") }
|
|
42
|
+
return validate_style(args.delete(inline).split("=", 2).last) if inline
|
|
43
|
+
|
|
44
|
+
index = args.index("--style")
|
|
45
|
+
return STYLES.first unless index
|
|
46
|
+
|
|
47
|
+
args.delete_at(index)
|
|
48
|
+
validate_style(args.delete_at(index) || raise(Error, USAGE))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Validates that *style* is a supported layout style.
|
|
52
|
+
def validate_style(style)
|
|
53
|
+
return style if STYLES.include?(style)
|
|
54
|
+
|
|
55
|
+
raise Error, "Unknown layout style: #{style.inspect} (available: #{STYLES.join(", ")})"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Replaces the application layout with the styled one. Creates it when missing,
|
|
59
|
+
# no-ops when already styled, and refuses to clobber local edits unless forced.
|
|
60
|
+
def write_layout
|
|
61
|
+
return create_file(layout_relative_path, styled_layout) unless File.exist?(layout_path)
|
|
62
|
+
return out.puts("identical #{layout_relative_path}") if current_layout == styled_layout
|
|
63
|
+
raise Error, "#{layout_relative_path} has local changes; re-run with --force to overwrite" unless replaceable_layout?
|
|
64
|
+
|
|
65
|
+
overwrite_layout
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# True when the current layout may be replaced: forced, or still the generated bare layout.
|
|
69
|
+
def replaceable_layout?
|
|
70
|
+
force? || current_layout == bare_layout
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def overwrite_layout
|
|
74
|
+
File.write(layout_path, styled_layout)
|
|
75
|
+
out.puts "overwrite #{layout_relative_path}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def current_layout
|
|
79
|
+
File.read(layout_path)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The rendered layout for the requested style.
|
|
83
|
+
def styled_layout
|
|
84
|
+
@styled_layout ||= render_template("layout/#{style}/application_layout.rb.template",
|
|
85
|
+
app_class: app_name.class_name,
|
|
86
|
+
app_name: app_name.class_name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The bare layout `charming new` generates, used to recognize unmodified apps.
|
|
90
|
+
def bare_layout
|
|
91
|
+
render_template("app/layout.template", app_class: app_name.class_name)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Inserts the focus ring, palette key binding, and palette commands into
|
|
95
|
+
# `ApplicationController`, idempotently.
|
|
96
|
+
def insert_controller_chrome
|
|
97
|
+
insert_before_end(application_controller_path, controller_chrome, "chrome", " end")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def controller_chrome
|
|
101
|
+
class_body_block(<<~RUBY)
|
|
102
|
+
focus_ring :sidebar, :content
|
|
103
|
+
|
|
104
|
+
key "ctrl+p", :open_command_palette, scope: :global
|
|
105
|
+
|
|
106
|
+
command "Home" do
|
|
107
|
+
navigate_to "/"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
command "Theme", :open_theme_palette
|
|
111
|
+
command "Close palette", :close_command_palette
|
|
112
|
+
command "Quit app", :quit
|
|
113
|
+
RUBY
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Inserts the built-in theme registration into the app's Application class, idempotently.
|
|
117
|
+
def insert_theme_registration
|
|
118
|
+
insert_before_end(application_config_path, theme_registration, "themes", " end")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def theme_registration
|
|
122
|
+
class_body_block(<<~RUBY)
|
|
123
|
+
Charming::UI::Theme.built_in_names.each do |theme_name|
|
|
124
|
+
theme theme_name.to_sym, built_in: theme_name
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
default_theme :phosphor
|
|
128
|
+
RUBY
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Indents *source* to class-body depth and prefixes a blank separator line, so the
|
|
132
|
+
# inserted block reads naturally after the existing declarations.
|
|
133
|
+
def class_body_block(source)
|
|
134
|
+
body = source.gsub(/^/, " ").gsub(/^ +$/, "").chomp
|
|
135
|
+
"\n#{body}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def layout_relative_path
|
|
139
|
+
File.join("app", "views", "layouts", "application_layout.rb")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def layout_path
|
|
143
|
+
File.join(destination, layout_relative_path)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def application_controller_path
|
|
147
|
+
File.join(destination, "app", "controllers", "application_controller.rb")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def application_config_path
|
|
151
|
+
File.join(destination, "lib", app_name.snake_name, "application.rb")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -152,35 +152,6 @@ module Charming
|
|
|
152
152
|
end)
|
|
153
153
|
insert_before_end(application_controller_path, command, "command", " end")
|
|
154
154
|
end
|
|
155
|
-
|
|
156
|
-
# Inserts *content* into *path* just before the line matching *end_line*. No-ops when
|
|
157
|
-
# the content is already present. Raises Error when the file or end-line is missing.
|
|
158
|
-
def insert_before_end(path, content, label, end_line)
|
|
159
|
-
raise Error, "Missing file: #{relative_path(path)}" unless File.exist?(path)
|
|
160
|
-
|
|
161
|
-
current = File.read(path)
|
|
162
|
-
return if current.include?(content)
|
|
163
|
-
|
|
164
|
-
lines = current.lines
|
|
165
|
-
index = insertion_index(lines, path, end_line)
|
|
166
|
-
lines.insert(index, "#{content}\n")
|
|
167
|
-
File.write(path, lines.join)
|
|
168
|
-
out.puts "insert #{label} #{relative_path(path)}"
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Returns the index of the last line in *lines* that matches *end_line* (the line
|
|
172
|
-
# just before which new content will be inserted). Raises Error when not found.
|
|
173
|
-
def insertion_index(lines, path, end_line)
|
|
174
|
-
index = lines.rindex { |line| line.chomp == end_line }
|
|
175
|
-
raise Error, "Could not update #{relative_path(path)}" unless index
|
|
176
|
-
|
|
177
|
-
index
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Strips the destination prefix from *path* for human-friendly status output.
|
|
181
|
-
def relative_path(path)
|
|
182
|
-
path.delete_prefix("#{destination}/")
|
|
183
|
-
end
|
|
184
155
|
end
|
|
185
156
|
end
|
|
186
157
|
end
|
|
@@ -2,8 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
A Charming terminal user interface.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
__DATABASE_SETUP__Run it with:
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
bundle exec __APP_SNAKE__
|
|
9
9
|
```
|
|
10
|
+
|
|
11
|
+
Until a route is defined the app boots to Charming's built-in welcome screen. Generate
|
|
12
|
+
your first screen (and make it home with `root` in `config/routes.rb`):
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bundle exec charming generate screen home
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Add the sidebar layout, command palette (`ctrl+p`), and theme switching (screens generated
|
|
19
|
+
with `charming generate screen` are reached through the command palette):
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bundle exec charming generate layout --style sidebar
|
|
23
|
+
```
|
|
@@ -3,11 +3,5 @@
|
|
|
3
3
|
module __APP_CLASS__
|
|
4
4
|
class Application < Charming::Application
|
|
5
5
|
root File.expand_path("../..", __dir__)
|
|
6
|
-
|
|
7
|
-
Charming::UI::Theme.built_in_names.each do |theme_name|
|
|
8
|
-
theme theme_name.to_sym, built_in: theme_name
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
default_theme :phosphor
|
|
12
6
|
end
|
|
13
7
|
end
|
|
@@ -3,17 +3,7 @@
|
|
|
3
3
|
module __APP_CLASS__
|
|
4
4
|
class ApplicationController < Charming::Controller
|
|
5
5
|
layout Layouts::ApplicationLayout
|
|
6
|
-
focus_ring :sidebar, :content
|
|
7
6
|
|
|
8
|
-
key "ctrl+p", :open_command_palette, scope: :global
|
|
9
7
|
key "q", :quit, scope: :global
|
|
10
|
-
|
|
11
|
-
command "Home" do
|
|
12
|
-
navigate_to "/"
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
command "Theme", :open_theme_palette
|
|
16
|
-
command "Close palette", :close_command_palette
|
|
17
|
-
command "Quit app", :quit
|
|
18
8
|
end
|
|
19
9
|
end
|
|
@@ -5,105 +5,16 @@ module __APP_CLASS__
|
|
|
5
5
|
class ApplicationLayout < Charming::View
|
|
6
6
|
def render
|
|
7
7
|
screen_layout(background: theme.background) do
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
column(app_title, navigation, shortcuts, gap: 1)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
pane(:content, grow: 1, border: :rounded, padding: [1, 2], style: content_style) do
|
|
14
|
-
yield_content
|
|
15
|
-
end
|
|
8
|
+
pane(style: centered) do
|
|
9
|
+
yield_content
|
|
16
10
|
end
|
|
17
|
-
|
|
18
|
-
overlay command_palette_modal if command_palette_modal
|
|
19
11
|
end
|
|
20
12
|
end
|
|
21
13
|
|
|
22
14
|
private
|
|
23
15
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def narrow?
|
|
29
|
-
screen.narrow?(below: 72, min_height: 20)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def sidebar_options
|
|
33
|
-
narrow? ? {height: [screen.height / 3, 5].max} : {width: 22}
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def sidebar_inner_width
|
|
37
|
-
narrow? ? [screen.width - 6, 20].max : 16
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def app_title
|
|
41
|
-
text "__APP_NAME__", style: theme.header_accent.align(:center).width(sidebar_inner_width)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def navigation
|
|
45
|
-
column(*nav_items)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def nav_items
|
|
49
|
-
controller.sidebar_routes.each_with_index.map do |route, index|
|
|
50
|
-
text nav_item_label(route, index), style: nav_item_style(route, index)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def nav_item_label(route, index)
|
|
55
|
-
cursor = (sidebar_focused? && index == sidebar_index) ? ">" : " "
|
|
56
|
-
active = current_route?(route) ? "\u{25cf}" : " "
|
|
57
|
-
"#{cursor} #{active} #{route.title}"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def nav_item_style(route, index)
|
|
61
|
-
if sidebar_focused? && index == sidebar_index
|
|
62
|
-
theme.selected
|
|
63
|
-
elsif current_route?(route)
|
|
64
|
-
theme.title
|
|
65
|
-
else
|
|
66
|
-
theme.muted
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def shortcuts
|
|
71
|
-
text "tab focus\nctrl+p commands\nq quit", style: theme.muted
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def sidebar_style
|
|
75
|
-
focused_style = sidebar_focused? ? theme.title : theme.border
|
|
76
|
-
palette_component ? focused_style.faint : focused_style
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def content_style
|
|
80
|
-
focused_style = content_focused? ? theme.title : theme.border
|
|
81
|
-
palette_component ? focused_style.faint : focused_style
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def command_palette_modal
|
|
85
|
-
return unless palette_component
|
|
86
|
-
|
|
87
|
-
render_component Charming::Components::CommandPaletteModal.new(
|
|
88
|
-
content: palette_component,
|
|
89
|
-
theme: theme
|
|
90
|
-
)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def sidebar_focused?
|
|
94
|
-
controller.sidebar_focused?
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def content_focused?
|
|
98
|
-
controller.content_focused?
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def sidebar_index
|
|
102
|
-
controller.sidebar_index
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def current_route?(route)
|
|
106
|
-
controller.current_route?(route)
|
|
16
|
+
def centered
|
|
17
|
+
style.align(:center).align_vertical(:middle)
|
|
107
18
|
end
|
|
108
19
|
end
|
|
109
20
|
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module __APP_CLASS__
|
|
4
|
+
module Layouts
|
|
5
|
+
class ApplicationLayout < Charming::View
|
|
6
|
+
def render
|
|
7
|
+
screen_layout(background: theme.background) do
|
|
8
|
+
split(narrow? ? :vertical : :horizontal, gap: 1) do
|
|
9
|
+
pane(:sidebar, **sidebar_options, border: :rounded, padding: [1, 2], style: sidebar_style) do
|
|
10
|
+
column(app_title, navigation, shortcuts, gap: 1)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
pane(:content, grow: 1, border: :rounded, padding: [1, 2], style: content_style) do
|
|
14
|
+
yield_content
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
overlay command_palette_modal if command_palette_modal
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def palette_component
|
|
25
|
+
assigns.fetch(:palette, nil)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def narrow?
|
|
29
|
+
screen.narrow?(below: 72, min_height: 20)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def sidebar_options
|
|
33
|
+
narrow? ? {height: [screen.height / 3, 5].max} : {width: 22}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sidebar_inner_width
|
|
37
|
+
narrow? ? [screen.width - 6, 20].max : 16
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def app_title
|
|
41
|
+
text "__APP_NAME__", style: theme.header_accent.align(:center).width(sidebar_inner_width)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def navigation
|
|
45
|
+
column(*nav_items)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def nav_items
|
|
49
|
+
controller.sidebar_routes.each_with_index.map do |route, index|
|
|
50
|
+
text nav_item_label(route, index), style: nav_item_style(route, index)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def nav_item_label(route, index)
|
|
55
|
+
cursor = (sidebar_focused? && index == sidebar_index) ? ">" : " "
|
|
56
|
+
active = current_route?(route) ? "\u{25cf}" : " "
|
|
57
|
+
"#{cursor} #{active} #{route.title}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def nav_item_style(route, index)
|
|
61
|
+
if sidebar_focused? && index == sidebar_index
|
|
62
|
+
theme.selected
|
|
63
|
+
elsif current_route?(route)
|
|
64
|
+
theme.title
|
|
65
|
+
else
|
|
66
|
+
theme.muted
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def shortcuts
|
|
71
|
+
text "tab focus\nctrl+p commands\nq quit", style: theme.muted
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def sidebar_style
|
|
75
|
+
focused_style = sidebar_focused? ? theme.title : theme.border
|
|
76
|
+
palette_component ? focused_style.faint : focused_style
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def content_style
|
|
80
|
+
focused_style = content_focused? ? theme.title : theme.border
|
|
81
|
+
palette_component ? focused_style.faint : focused_style
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def command_palette_modal
|
|
85
|
+
return unless palette_component
|
|
86
|
+
|
|
87
|
+
render_component Charming::Components::CommandPaletteModal.new(
|
|
88
|
+
content: palette_component,
|
|
89
|
+
theme: theme
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def sidebar_focused?
|
|
94
|
+
controller.sidebar_focused?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def content_focused?
|
|
98
|
+
controller.content_focused?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def sidebar_index
|
|
102
|
+
controller.sidebar_index
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def current_route?(route)
|
|
106
|
+
controller.current_route?(route)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|