trmnl_preview 0.3.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +55 -21
- data/bin/trmnlp +14 -0
- data/lib/trmnlp/api_client.rb +62 -0
- data/lib/trmnlp/app.rb +98 -0
- data/lib/trmnlp/cli.rb +51 -0
- data/lib/trmnlp/commands/base.rb +23 -0
- data/lib/trmnlp/commands/build.rb +22 -0
- data/lib/trmnlp/commands/login.rb +25 -0
- data/lib/trmnlp/commands/pull.rb +45 -0
- data/lib/trmnlp/commands/push.rb +43 -0
- data/lib/trmnlp/commands/serve.rb +25 -0
- data/lib/trmnlp/commands.rb +1 -0
- data/lib/trmnlp/config/app.rb +39 -0
- data/lib/trmnlp/config/plugin.rb +74 -0
- data/lib/trmnlp/config/project.rb +47 -0
- data/lib/trmnlp/config.rb +15 -0
- data/lib/trmnlp/context.rb +211 -0
- data/lib/trmnlp/custom_filters.rb +14 -0
- data/lib/trmnlp/paths.rb +50 -0
- data/lib/trmnlp/screen_generator.rb +137 -0
- data/lib/trmnlp/version.rb +5 -0
- data/lib/trmnlp.rb +13 -0
- data/trmnl_preview.gemspec +32 -14
- data/web/public/index.css +98 -0
- data/web/public/index.js +75 -0
- data/web/views/index.erb +15 -80
- data/web/views/{render_view.erb → render_html.erb} +1 -6
- metadata +148 -26
- data/.ruby-version +0 -1
- data/config.example.toml +0 -14
- data/docs/preview.png +0 -0
- data/exe/trmnlp +0 -12
- data/lib/trmnl_preview/app.rb +0 -78
- data/lib/trmnl_preview/cmd/build.rb +0 -25
- data/lib/trmnl_preview/cmd/serve.rb +0 -31
- data/lib/trmnl_preview/cmd/usage.rb +0 -10
- data/lib/trmnl_preview/context.rb +0 -129
- data/lib/trmnl_preview/liquid_filters.rb +0 -8
- data/lib/trmnl_preview/version.rb +0 -5
- data/lib/trmnl_preview.rb +0 -10
- data/web/public/live-render.js +0 -23
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module TRMNLP
|
4
|
+
class Config
|
5
|
+
class Plugin
|
6
|
+
def initialize(paths, trmnlp_config)
|
7
|
+
@paths = paths
|
8
|
+
@trmnlp_config = trmnlp_config
|
9
|
+
reload!
|
10
|
+
end
|
11
|
+
|
12
|
+
def reload!
|
13
|
+
if paths.plugin_config.exist?
|
14
|
+
@config = YAML.load_file(paths.plugin_config)
|
15
|
+
else
|
16
|
+
@config = {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def strategy = @config['strategy']
|
21
|
+
def polling? = strategy == 'polling'
|
22
|
+
def webhook? = strategy == 'webhook'
|
23
|
+
def static? = strategy == 'static'
|
24
|
+
|
25
|
+
def polling_urls
|
26
|
+
return [] if @config['polling_url'].nil? || @config['polling_url'].empty?
|
27
|
+
|
28
|
+
urls = @config['polling_url'].split("\n").map(&:strip)
|
29
|
+
|
30
|
+
urls.map { |url| with_custom_fields(url) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def polling_url_text = polling_urls.join("\r\n") # for {{ trmnl }}
|
34
|
+
|
35
|
+
def polling_verb = @config['polling_verb'] || 'GET'
|
36
|
+
|
37
|
+
def polling_headers
|
38
|
+
string_to_hash(@config['polling_headers'] || '').transform_values { |v| with_custom_fields(v) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def polling_headers_encoded = polling_headers.map { |k, v| "#{k}=#{v}" }.join('&') # for {{ trmnl }}
|
42
|
+
|
43
|
+
def polling_body = with_custom_fields(@config['polling_body'] || '')
|
44
|
+
|
45
|
+
def dark_mode = @config['dark_mode'] || 'no'
|
46
|
+
|
47
|
+
def no_screen_padding = @config['no_screen_padding'] || 'no'
|
48
|
+
|
49
|
+
def id = @config['id']
|
50
|
+
|
51
|
+
def static_data
|
52
|
+
JSON.parse(@config['static_data'] || '{}')
|
53
|
+
rescue JSON::ParserError
|
54
|
+
raise Error, 'invalid JSON in static_data'
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :paths, :trmnlp_config
|
60
|
+
|
61
|
+
def with_custom_fields(value) = trmnlp_config.with_custom_fields(value)
|
62
|
+
|
63
|
+
# copied from TRMNL core
|
64
|
+
def string_to_hash(str, delimiter: '=')
|
65
|
+
str.split('&').map do |k_v|
|
66
|
+
key, value = k_v.split(delimiter)
|
67
|
+
next if value.nil?
|
68
|
+
|
69
|
+
{ key => CGI.unescape_uri_component(value) }
|
70
|
+
end.compact.reduce({}, :merge)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module TRMNLP
|
4
|
+
class Config
|
5
|
+
class Project
|
6
|
+
attr_reader :paths
|
7
|
+
|
8
|
+
def initialize(paths)
|
9
|
+
@paths = paths
|
10
|
+
reload!
|
11
|
+
end
|
12
|
+
|
13
|
+
def reload!
|
14
|
+
if paths.trmnlp_config.exist?
|
15
|
+
@config = YAML.load_file(paths.trmnlp_config)
|
16
|
+
else
|
17
|
+
@config = {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def user_filters = @config['custom_filters'] || []
|
22
|
+
|
23
|
+
def live_render? = !watch_paths.empty?
|
24
|
+
|
25
|
+
def watch_paths
|
26
|
+
(@config['watch'] || []).map { |watch_path| paths.expand(watch_path) }.uniq
|
27
|
+
end
|
28
|
+
|
29
|
+
def custom_fields = @config['custom_fields'] || {}
|
30
|
+
|
31
|
+
def user_data_overrides = @config['variables'] || {}
|
32
|
+
|
33
|
+
# for interpolating custom_fields into polling_* options
|
34
|
+
def with_custom_fields(value)
|
35
|
+
custom_fields_with_env = custom_fields.transform_values { |v| with_env(v) }
|
36
|
+
Liquid::Template.parse(value).render(custom_fields_with_env)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# for interpolating ENV vars into custom_fields
|
42
|
+
def with_env(value)
|
43
|
+
Liquid::Template.parse(value).render({ 'env' => ENV.to_h })
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'config/app'
|
2
|
+
require_relative 'config/plugin'
|
3
|
+
require_relative 'config/project'
|
4
|
+
|
5
|
+
module TRMNLP
|
6
|
+
class Config
|
7
|
+
attr_reader :app, :project, :plugin
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@app = App.new(path)
|
11
|
+
@project = Project.new(path)
|
12
|
+
@plugin = Plugin.new(path, @project)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'faraday'
|
3
|
+
require 'filewatcher'
|
4
|
+
require 'json'
|
5
|
+
require 'liquid'
|
6
|
+
|
7
|
+
require_relative 'config'
|
8
|
+
require_relative 'custom_filters'
|
9
|
+
require_relative 'paths'
|
10
|
+
|
11
|
+
module TRMNLP
|
12
|
+
class Context
|
13
|
+
attr_reader :config, :paths
|
14
|
+
|
15
|
+
def initialize(root_dir)
|
16
|
+
@paths = Paths.new(root_dir)
|
17
|
+
@config = Config.new(paths)
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!
|
21
|
+
raise Error, "not a plugin directory (did not find #{paths.trmnlp_config})" unless paths.valid?
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_filewatcher
|
25
|
+
@filewatcher_thread ||= Thread.new do
|
26
|
+
loop do
|
27
|
+
begin
|
28
|
+
Filewatcher.new(config.project.watch_paths).watch do |changes|
|
29
|
+
config.project.reload!
|
30
|
+
config.plugin.reload!
|
31
|
+
new_user_data = user_data
|
32
|
+
|
33
|
+
views = changes.map { |path, _change| File.basename(path, '.liquid') }
|
34
|
+
views.each do |view|
|
35
|
+
@view_change_callback.call(view, new_user_data) if @view_change_callback
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
puts "Error during live render: #{e}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_view_change(&block)
|
46
|
+
@view_change_callback = block
|
47
|
+
end
|
48
|
+
|
49
|
+
def user_data
|
50
|
+
merged_data = base_trmnl_data
|
51
|
+
|
52
|
+
if config.plugin.static?
|
53
|
+
merged_data.merge!(config.plugin.static_data)
|
54
|
+
elsif paths.user_data.exist?
|
55
|
+
merged_data.merge!(JSON.parse(paths.user_data.read))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Praise be to ActiveSupport
|
59
|
+
merged_data.deep_merge!(config.project.user_data_overrides)
|
60
|
+
end
|
61
|
+
|
62
|
+
def poll_data
|
63
|
+
return unless config.plugin.polling?
|
64
|
+
|
65
|
+
data = {}
|
66
|
+
|
67
|
+
if config.plugin.polling_urls.empty?
|
68
|
+
raise Error, "config must specify polling_url or polling_urls"
|
69
|
+
end
|
70
|
+
|
71
|
+
config.plugin.polling_urls.each.with_index do |url, i|
|
72
|
+
verb = config.plugin.polling_verb.upcase
|
73
|
+
|
74
|
+
print "#{verb} #{url}... "
|
75
|
+
|
76
|
+
conn = Faraday.new(url:, headers: config.plugin.polling_headers)
|
77
|
+
|
78
|
+
case verb
|
79
|
+
when 'GET'
|
80
|
+
response = conn.get
|
81
|
+
when 'POST'
|
82
|
+
response = conn.post do |req|
|
83
|
+
req.body = config.plugin.polling_body
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "received #{response.body.length} bytes (#{response.status} status)"
|
88
|
+
if response.status == 200
|
89
|
+
json = wrap_array(JSON.parse(response.body))
|
90
|
+
else
|
91
|
+
json = {}
|
92
|
+
puts response.body
|
93
|
+
end
|
94
|
+
|
95
|
+
if config.plugin.polling_urls.count == 1
|
96
|
+
# For a single polling URL, we just return the JSON directly
|
97
|
+
data = json
|
98
|
+
break
|
99
|
+
else
|
100
|
+
# Multiple URLs are namespaced by index
|
101
|
+
data["IDX_#{i}"] = json
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
write_user_data(data)
|
106
|
+
|
107
|
+
data
|
108
|
+
rescue StandardError => e
|
109
|
+
puts "error: #{e.message}"
|
110
|
+
{}
|
111
|
+
end
|
112
|
+
|
113
|
+
def put_webhook(payload)
|
114
|
+
data = wrap_array(JSON.parse(payload))
|
115
|
+
write_user_data(data)
|
116
|
+
rescue
|
117
|
+
puts "webhook error: #{e.message}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def render_template(view)
|
121
|
+
template_path = paths.template(view)
|
122
|
+
return "Missing template: #{template_path}" unless template_path.exist?
|
123
|
+
|
124
|
+
user_template = Liquid::Template.parse(template_path.read, environment: liquid_environment)
|
125
|
+
user_template.render(user_data)
|
126
|
+
rescue StandardError => e
|
127
|
+
e.message
|
128
|
+
end
|
129
|
+
|
130
|
+
def render_full_page(view)
|
131
|
+
template = paths.render_template.read
|
132
|
+
|
133
|
+
ERB.new(template).result(TemplateBinding.new(self, view).get_binding do
|
134
|
+
render_template(view)
|
135
|
+
end)
|
136
|
+
end
|
137
|
+
|
138
|
+
def screen_classes
|
139
|
+
classes = 'screen'
|
140
|
+
classes << ' screen--no-bleed' if config.plugin.no_screen_padding == 'yes'
|
141
|
+
classes << ' dark-mode' if config.plugin.dark_mode == 'yes'
|
142
|
+
classes
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# bindings must match the `GET /render/{view}.html` route in app.rb
|
148
|
+
class TemplateBinding
|
149
|
+
def initialize(context, view)
|
150
|
+
@screen_classes = context.screen_classes
|
151
|
+
@view = view
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_binding = binding
|
155
|
+
end
|
156
|
+
|
157
|
+
def wrap_array(json)
|
158
|
+
json.is_a?(Array) ? { data: json } : json
|
159
|
+
end
|
160
|
+
|
161
|
+
def base_trmnl_data
|
162
|
+
{
|
163
|
+
'trmnl' => {
|
164
|
+
'user' => {
|
165
|
+
'name' => 'name',
|
166
|
+
'first_name' => 'first_name',
|
167
|
+
'last_name' => 'last_name',
|
168
|
+
'locale' => 'en',
|
169
|
+
'time_zone' => 'Eastern Time (US & Canada)',
|
170
|
+
'time_zone_iana' => 'America/New_York',
|
171
|
+
'utc_offset' => -14400
|
172
|
+
},
|
173
|
+
'device' => {
|
174
|
+
'friendly_id' => 'ABC123',
|
175
|
+
'percent_charged' => 85.0,
|
176
|
+
'wifi_strength' => 90,
|
177
|
+
'height' => 480,
|
178
|
+
'width' => 800
|
179
|
+
},
|
180
|
+
'system' => {
|
181
|
+
'timestamp_utc' => Time.now.utc.to_i,
|
182
|
+
},
|
183
|
+
'plugin_settings' => {
|
184
|
+
'instance_name' => 'instance_name',
|
185
|
+
'strategy' => config.plugin.strategy,
|
186
|
+
'dark_mode' => config.plugin.dark_mode,
|
187
|
+
'polling_headers' => config.plugin.polling_headers_encoded,
|
188
|
+
'polling_url' => config.plugin.polling_url_text,
|
189
|
+
'custom_fields_values' => config.project.custom_fields
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
def liquid_environment
|
196
|
+
@liquid_environment ||= Liquid::Environment.build do |env|
|
197
|
+
env.register_filter(CustomFilters)
|
198
|
+
|
199
|
+
config.project.user_filters.each do |module_name, relative_path|
|
200
|
+
require paths.root_dir.join(relative_path)
|
201
|
+
env.register_filter(Object.const_get(module_name))
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def write_user_data(data)
|
207
|
+
paths.create_cache_dir
|
208
|
+
paths.user_data.write(JSON.generate(data))
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module TRMNLP
|
4
|
+
module CustomFilters
|
5
|
+
# TODO: sync up with core
|
6
|
+
def number_with_delimiter(*args)
|
7
|
+
ActiveSupport::NumberHelper.number_to_delimited(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def number_to_currency(*args)
|
11
|
+
ActiveSupport::NumberHelper.number_to_currency(*args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/trmnlp/paths.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'xdg'
|
2
|
+
|
3
|
+
module TRMNLP
|
4
|
+
class Paths
|
5
|
+
attr_reader :root_dir
|
6
|
+
|
7
|
+
def initialize(root_dir)
|
8
|
+
@root_dir = Pathname.new(root_dir)
|
9
|
+
@xdg = XDG.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# --- directories ---
|
13
|
+
|
14
|
+
def src_dir = root_dir.join('src')
|
15
|
+
|
16
|
+
def build_dir = root_dir.join('_build')
|
17
|
+
def create_build_dir = build_dir.mkpath
|
18
|
+
|
19
|
+
def app_config_dir = xdg.config_home.join('trmnlp')
|
20
|
+
|
21
|
+
def cache_dir = xdg.cache_home.join('trmnl')
|
22
|
+
def create_cache_dir = cache_dir.mkpath
|
23
|
+
|
24
|
+
def valid? = trmnlp_config.exist?
|
25
|
+
|
26
|
+
# --- files ---
|
27
|
+
|
28
|
+
def trmnlp_config = root_dir.join('.trmnlp.yml')
|
29
|
+
|
30
|
+
def plugin_config = src_dir.join('settings.yml')
|
31
|
+
|
32
|
+
def template(view) = src_dir.join("#{view}.liquid")
|
33
|
+
|
34
|
+
def app_config = app_config_dir.join('config.yml')
|
35
|
+
|
36
|
+
def user_data = cache_dir.join('data.json')
|
37
|
+
|
38
|
+
def render_template = Pathname.new(__dir__).join('..', '..', 'web', 'views', 'render_html.erb')
|
39
|
+
|
40
|
+
def src_files = src_dir.glob('*').select(&:file?)
|
41
|
+
|
42
|
+
# --- utilities ---
|
43
|
+
|
44
|
+
def expand(path) = Pathname.new(path).expand_path(root_dir)
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :xdg
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'ferrum'
|
2
|
+
require 'mini_magick'
|
3
|
+
require 'puppeteer-ruby'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module TRMNLP
|
7
|
+
class ScreenGenerator
|
8
|
+
|
9
|
+
def initialize(html, opts = {})
|
10
|
+
self.input = html
|
11
|
+
self.image = !!opts[:image]
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :input, :output, :image, :processor, :img_path
|
15
|
+
|
16
|
+
def process
|
17
|
+
convert_to_image
|
18
|
+
image ? mono_image(output) : mono(output)
|
19
|
+
output.path
|
20
|
+
# IO.copy_stream(output, img_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# def img_path
|
26
|
+
# "#{Dir.pwd}/public/images/generated/#{SecureRandom.hex(3)}.bmp"
|
27
|
+
# end
|
28
|
+
|
29
|
+
# Constructs the command and passes the input to the vendor/puppeteer.js
|
30
|
+
# script for processing. Returns a base64 encoded string
|
31
|
+
def convert_to_image
|
32
|
+
retry_count = 0
|
33
|
+
begin
|
34
|
+
# context = browser_instance.create_incognito_browser_context
|
35
|
+
page = firefox_browser.new_page
|
36
|
+
page.viewport = Puppeteer::Viewport.new(width: width, height: height)
|
37
|
+
# NOTE: Use below for chromium
|
38
|
+
# page.set_content(input, wait_until: ['networkidle0', 'domcontentloaded'])
|
39
|
+
# Note: Use below for firefox
|
40
|
+
page.set_content(input, timeout: 10000)
|
41
|
+
page.evaluate(<<~JAVASCRIPT)
|
42
|
+
() => {
|
43
|
+
document.getElementsByTagName('html')[0].style.overflow = "hidden";
|
44
|
+
document.getElementsByTagName('body')[0].style.overflow = "hidden";
|
45
|
+
}
|
46
|
+
JAVASCRIPT
|
47
|
+
self.output = Tempfile.new
|
48
|
+
page.screenshot(path: output.path, type: 'png')
|
49
|
+
firefox_browser.close
|
50
|
+
end
|
51
|
+
rescue Puppeteer::TimeoutError, Puppeteer::FrameManager::NavigationError => e
|
52
|
+
retry_count += 1
|
53
|
+
firefox_browser.close
|
54
|
+
if retry_count <= 1
|
55
|
+
@browser = nil
|
56
|
+
retry
|
57
|
+
else
|
58
|
+
puts "ERROR -> Converter::Html#convert_to_image_by_firefox -> #{e.message}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Refer this PR where the author reused the browser instance https://github.com/YusukeIwaki/puppeteer-ruby/pull/100/files
|
63
|
+
# This will increase the throughput of our image rendering process by 60-70%, saving about ~1.5 second per image generation.
|
64
|
+
# On local it takes < 1 second now to generate the subsequent image.
|
65
|
+
def firefox_browser
|
66
|
+
@browser ||= Puppeteer.launch(
|
67
|
+
product: 'firefox',
|
68
|
+
headless: true,
|
69
|
+
args: [
|
70
|
+
"--window-size=#{width},#{height}",
|
71
|
+
"--disable-web-security"
|
72
|
+
# "--hide-scrollbars" #works only on chrome, using page.evaluate for firefox
|
73
|
+
]
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def Ferrum.cached_browser
|
78
|
+
return nil unless $cached_browser
|
79
|
+
|
80
|
+
$cached_browser
|
81
|
+
end
|
82
|
+
|
83
|
+
def Ferrum.cached_browser=(value)
|
84
|
+
$cached_browser = value
|
85
|
+
end
|
86
|
+
|
87
|
+
# Overall at max wait for 2.5 seconds
|
88
|
+
def wait_for_stop_loading(page)
|
89
|
+
count = 0
|
90
|
+
while page.frames.first.state != :stopped_loading && count < 20
|
91
|
+
count += 1
|
92
|
+
sleep 0.1
|
93
|
+
end
|
94
|
+
sleep 0.5 # wait_until: DomContentLoaded event is not available in ferrum
|
95
|
+
end
|
96
|
+
|
97
|
+
def mono(img)
|
98
|
+
MiniMagick::Tool::Convert.new do |m|
|
99
|
+
m << img.path
|
100
|
+
m.monochrome # Use built-in smart monochrome dithering (but it's not working as expected)
|
101
|
+
m.depth(color_depth) # Should be set to 1 for 1-bit output
|
102
|
+
m.strip # Remove any additional metadata
|
103
|
+
m << ('bmp3:' << img.path)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def mono_image(img)
|
108
|
+
# Changelog:
|
109
|
+
# ImageMagick 6.XX used to convert the png to bitmap with dithering while maintaining the channel to 1
|
110
|
+
# The same seems to be broken with imagemagick 7.XX
|
111
|
+
# So in order to reduce the channel from 8 to 1, I just rerun the command, and it's working
|
112
|
+
# TODO for future, find a better way to generate image screens.
|
113
|
+
MiniMagick::Tool::Convert.new do |m|
|
114
|
+
m << img.path
|
115
|
+
m.dither << 'FloydSteinberg'
|
116
|
+
m.remap << 'pattern:gray50'
|
117
|
+
m.depth(color_depth) # Should be set to 1 for 1-bit output
|
118
|
+
m.strip # Remove any additional metadata
|
119
|
+
m << ('bmp3:' << img.path) # Converts to Bitmap.
|
120
|
+
end
|
121
|
+
MiniMagick::Tool::Convert.new do |m|
|
122
|
+
m << img.path
|
123
|
+
m.dither << 'FloydSteinberg'
|
124
|
+
m.remap << 'pattern:gray50'
|
125
|
+
m.depth(color_depth) # Should be set to 1 for 1-bit output
|
126
|
+
m.strip # Remove any additional metadata
|
127
|
+
m << ('bmp3:' << img.path) # Converts to Bitmap.
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def width = 800
|
132
|
+
|
133
|
+
def height = 480
|
134
|
+
|
135
|
+
def color_depth = 1
|
136
|
+
end
|
137
|
+
end
|
data/lib/trmnlp.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TRMNLP; end
|
4
|
+
|
5
|
+
require_relative "trmnlp/config"
|
6
|
+
require_relative "trmnlp/context"
|
7
|
+
require_relative "trmnlp/version"
|
8
|
+
|
9
|
+
module TRMNLP
|
10
|
+
VIEWS = %w{full half_horizontal half_vertical quadrant}
|
11
|
+
|
12
|
+
class Error < StandardError; end
|
13
|
+
end
|
data/trmnl_preview.gemspec
CHANGED
@@ -1,44 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "lib/
|
3
|
+
require_relative "lib/trmnlp/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "trmnl_preview"
|
7
|
-
spec.version =
|
7
|
+
spec.version = TRMNLP::VERSION
|
8
8
|
spec.authors = ["Rockwell Schrock"]
|
9
9
|
spec.email = ["rockwell@schrock.me"]
|
10
10
|
|
11
11
|
spec.summary = "Local web server to preview TRMNL plugins"
|
12
12
|
spec.description = "Automatically rebuild and preview TRNML plugins in multiple views"
|
13
|
-
spec.homepage = "https://github.com/
|
13
|
+
spec.homepage = "https://github.com/usetrmnl/trmnlp"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = ">= 3.0.0"
|
16
16
|
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
18
|
|
19
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
-
spec.metadata["source_code_uri"] = "https://github.com/
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/usetrmnl/trmnlp"
|
21
21
|
|
22
|
-
# Specify which files should be added to the gem when it is released.
|
23
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
22
|
spec.files = Dir.chdir(__dir__) do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
[
|
24
|
+
'bin/**/*',
|
25
|
+
'lib/**/*',
|
26
|
+
'web/**/*',
|
27
|
+
'CHANGELOG.md',
|
28
|
+
'LICENSE.txt',
|
29
|
+
'README.md',
|
30
|
+
'trmnl_preview.gemspec'
|
31
|
+
].flat_map { |glob| Dir[glob] }
|
29
32
|
end
|
30
|
-
spec.bindir = "
|
33
|
+
spec.bindir = "bin"
|
31
34
|
spec.executables = ["trmnlp"]
|
32
35
|
spec.require_paths = ["lib"]
|
33
36
|
|
34
|
-
|
37
|
+
|
38
|
+
# Web server
|
35
39
|
spec.add_dependency "sinatra", "~> 4.1"
|
36
40
|
spec.add_dependency "rackup", "~> 2.2"
|
37
41
|
spec.add_dependency "puma", "~> 6.5"
|
42
|
+
spec.add_dependency "faye-websocket", "~> 0.11.3"
|
43
|
+
|
44
|
+
# HTML rendering
|
38
45
|
spec.add_dependency "liquid", "~> 5.6"
|
39
|
-
spec.add_dependency "
|
46
|
+
spec.add_dependency "activesupport", "~> 8.0"
|
47
|
+
|
48
|
+
# BMP rendering
|
49
|
+
spec.add_dependency "ferrum", "~> 0.16"
|
50
|
+
spec.add_dependency 'puppeteer-ruby', '~> 0.45.6'
|
51
|
+
spec.add_dependency 'mini_magick', '~> 4.12.0'
|
52
|
+
|
53
|
+
# Utilities
|
40
54
|
spec.add_dependency "filewatcher", "~> 2.1"
|
41
|
-
spec.add_dependency "
|
55
|
+
spec.add_dependency "faraday", "~> 2.1"
|
56
|
+
spec.add_dependency "faraday-multipart", "~> 1.1"
|
57
|
+
spec.add_dependency "xdg", "~> 9.1"
|
58
|
+
spec.add_dependency "rubyzip", "~> 2.3.0"
|
59
|
+
spec.add_dependency "thor", "~> 1.3"
|
42
60
|
|
43
61
|
# For more information and examples about making a new gem, check out our
|
44
62
|
# guide at: https://bundler.io/guides/creating_gem.html
|