trmnl_preview 0.3.1 → 0.3.2
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 +10 -0
- data/README.md +11 -6
- data/exe/trmnlp +2 -0
- data/lib/trmnl_preview/app.rb +18 -10
- data/lib/trmnl_preview/cmd/usage.rb +2 -1
- data/lib/trmnl_preview/cmd/version.rb +1 -0
- data/lib/trmnl_preview/context.rb +14 -8
- data/lib/trmnl_preview/custom_filters.rb +13 -0
- data/lib/trmnl_preview/screen_generator.rb +137 -0
- data/lib/trmnl_preview/version.rb +1 -1
- data/trmnl_preview.gemspec +13 -2
- data/web/public/index.css +98 -0
- data/web/public/index.js +71 -0
- data/web/views/index.erb +13 -80
- data/web/views/{render_view.erb → render_html.erb} +0 -5
- metadata +73 -14
- data/lib/trmnl_preview/liquid_filters.rb +0 -8
- data/web/public/live-render.js +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e965687c88e23b15e8727f9b871b6a2529bc28c388fa2e8733fb5034915ec405
|
4
|
+
data.tar.gz: c1299d3ce7b27a13be79d0018535c8f1b64171f7154d9dd1f425eeacbcbc0129
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d507258ba8a315daae87b83dc5609f2aa99c9d1c674f7067cd2b67b84382a4746c8f299c7c3d0e3e9cc4f378846ca3f5696f4aa8f1dcb65c6eec8b6e44f46d74
|
7
|
+
data.tar.gz: 02231d9584b0b24a4b7b2749c85cbf14af2c3b6d5321c63dc03bf6e8bb7655b03ee37d86ba69b7ac16ebb3acb367cad2bf54ec437e405a1a842eb1f9263090f3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.3.2
|
4
|
+
|
5
|
+
- Add bitmap rendering
|
6
|
+
- Add TRMNL's [custom plugin filters](https://help.usetrmnl.com/en/articles/10347358-custom-plugin-filters)
|
7
|
+
- Add support for user-supplied custom filters
|
8
|
+
|
9
|
+
## 0.3.1
|
10
|
+
|
11
|
+
- Add live render
|
12
|
+
|
3
13
|
## 0.3.0
|
4
14
|
|
5
15
|
- Add poll button
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# trmnlp
|
2
2
|
|
3
3
|
A basic self-hosted web server to ease the development and sharing of [TRMNL](https://usetrmnl.com/) plugins.
|
4
4
|
|
5
|
-
[Liquid](https://shopify.github.io/liquid/) templates are rendered
|
5
|
+
[Liquid](https://shopify.github.io/liquid/) templates are rendered leveraging the [TRMNL Design System](https://usetrmnl.com/framework). They may be generated as HTML (faster, and a good approximation of the final result) or as BMP images (slower, but more accurate).
|
6
6
|
|
7
7
|
The server watches the filesystem for changes to the Liquid templates, seamlessly updating the preview without the need to refresh.
|
8
8
|
|
@@ -36,17 +36,22 @@ docker run \
|
|
36
36
|
|
37
37
|
## Running the Server (Local Ruby)
|
38
38
|
|
39
|
-
|
39
|
+
Prerequisites:
|
40
|
+
|
41
|
+
- Ruby 3.x
|
42
|
+
- For BMP rendering (optional):
|
43
|
+
- Firefox
|
44
|
+
- ImageMagick
|
45
|
+
|
46
|
+
In the plugin repository:
|
40
47
|
|
41
48
|
```sh
|
42
|
-
|
49
|
+
gem install trmnl_preview
|
43
50
|
trmnlp serve # Starts the server
|
44
51
|
```
|
45
52
|
|
46
53
|
## Usage Notes
|
47
54
|
|
48
|
-
Simply refresh the page to re-render.
|
49
|
-
|
50
55
|
When the strategy is "polling", the specified URL will be fetched once, when the server starts.
|
51
56
|
|
52
57
|
When the strategy is "webhook", payloads can be POSTed to the `/webhook` endpoint. They are saved to `tmp/data.json` for future renders.
|
data/exe/trmnlp
CHANGED
@@ -7,6 +7,8 @@ when 'serve'
|
|
7
7
|
require_relative '../lib/trmnl_preview/cmd/serve'
|
8
8
|
when 'build'
|
9
9
|
require_relative '../lib/trmnl_preview/cmd/build'
|
10
|
+
when 'version'
|
11
|
+
require_relative '../lib/trmnl_preview/cmd/version'
|
10
12
|
else
|
11
13
|
require_relative '../lib/trmnl_preview/cmd/usage'
|
12
14
|
end
|
data/lib/trmnl_preview/app.rb
CHANGED
@@ -4,6 +4,7 @@ require 'sinatra'
|
|
4
4
|
require 'sinatra/base'
|
5
5
|
|
6
6
|
require_relative 'context'
|
7
|
+
require_relative 'screen_generator'
|
7
8
|
|
8
9
|
module TRMNLPreview
|
9
10
|
class App < Sinatra::Base
|
@@ -23,10 +24,10 @@ module TRMNLPreview
|
|
23
24
|
|
24
25
|
@context.poll_data if @context.strategy == 'polling'
|
25
26
|
|
26
|
-
@
|
27
|
+
@live_reload_clients = []
|
27
28
|
@context.on_view_change do |view|
|
28
|
-
@
|
29
|
-
ws.send(
|
29
|
+
@live_reload_clients.each do |ws|
|
30
|
+
ws.send('reload')
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -40,16 +41,15 @@ module TRMNLPreview
|
|
40
41
|
redirect '/full'
|
41
42
|
end
|
42
43
|
|
43
|
-
get '/
|
44
|
+
get '/live_reload' do
|
44
45
|
ws = Faye::WebSocket.new(request.env)
|
45
|
-
view = params['view']
|
46
46
|
|
47
47
|
ws.on(:open) do |event|
|
48
|
-
@
|
48
|
+
@live_reload_clients << ws
|
49
49
|
end
|
50
50
|
|
51
51
|
ws.on(:close) do |event|
|
52
|
-
@
|
52
|
+
@live_reload_clients.delete(ws)
|
53
53
|
end
|
54
54
|
|
55
55
|
ws.rack_response
|
@@ -63,16 +63,24 @@ module TRMNLPreview
|
|
63
63
|
VIEWS.each do |view|
|
64
64
|
get "/#{view}" do
|
65
65
|
@view = view
|
66
|
+
@live_reload = @context.live_render
|
66
67
|
erb :index
|
67
68
|
end
|
68
69
|
|
69
|
-
get "/render/#{view}" do
|
70
|
+
get "/render/#{view}.html" do
|
70
71
|
@view = view
|
71
|
-
|
72
|
-
erb :render_view do
|
72
|
+
erb :render_html do
|
73
73
|
@context.render_template(view)
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
get "/render/#{view}.bmp" do
|
78
|
+
@view = view
|
79
|
+
html = @context.render_full_page(view)
|
80
|
+
generator = ScreenGenerator.new(html, image: true)
|
81
|
+
img_path = generator.process
|
82
|
+
send_file img_path, type: 'image/png', disposition: 'inline'
|
83
|
+
end
|
76
84
|
end
|
77
85
|
end
|
78
86
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
puts TRMNLPreview::VERSION
|
@@ -6,21 +6,17 @@ require 'liquid'
|
|
6
6
|
require 'open-uri'
|
7
7
|
require 'toml-rb'
|
8
8
|
|
9
|
-
require_relative '
|
9
|
+
require_relative 'custom_filters'
|
10
10
|
|
11
11
|
module TRMNLPreview
|
12
12
|
class Context
|
13
13
|
attr_reader :strategy, :temp_dir, :live_render
|
14
14
|
|
15
|
-
def initialize(root)
|
15
|
+
def initialize(root, opts = {})
|
16
16
|
config_path = File.join(root, 'config.toml')
|
17
17
|
@user_views_dir = File.join(root, 'views')
|
18
18
|
@temp_dir = File.join(root, 'tmp')
|
19
19
|
@data_json_path = File.join(@temp_dir, 'data.json')
|
20
|
-
|
21
|
-
@liquid_environment = Liquid::Environment.build do |env|
|
22
|
-
env.register_filter(LiquidFilters)
|
23
|
-
end
|
24
20
|
|
25
21
|
unless File.exist?(config_path)
|
26
22
|
raise "No config.toml found in #{root}"
|
@@ -35,13 +31,23 @@ module TRMNLPreview
|
|
35
31
|
@url = config['url']
|
36
32
|
@polling_headers = config['polling_headers'] || {}
|
37
33
|
@live_render = config['live_render'] != false
|
34
|
+
@user_filters = config['custom_filters'] || []
|
38
35
|
|
39
36
|
unless ['polling', 'webhook'].include?(@strategy)
|
40
37
|
raise "Invalid strategy: #{strategy} (must be 'polling' or 'webhook')"
|
41
38
|
end
|
42
|
-
|
39
|
+
|
43
40
|
FileUtils.mkdir_p(@temp_dir)
|
44
41
|
|
42
|
+
@liquid_environment = Liquid::Environment.build do |env|
|
43
|
+
env.register_filter(CustomFilters)
|
44
|
+
|
45
|
+
@user_filters.each do |module_name, relative_path|
|
46
|
+
require File.join(root, relative_path)
|
47
|
+
env.register_filter(Object.const_get(module_name))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
45
51
|
start_filewatcher_thread if @live_render
|
46
52
|
end
|
47
53
|
|
@@ -112,7 +118,7 @@ module TRMNLPreview
|
|
112
118
|
end
|
113
119
|
|
114
120
|
def render_full_page(view)
|
115
|
-
page_erb_template = File.read(File.join(__dir__, '..', '..', 'web', 'views', '
|
121
|
+
page_erb_template = File.read(File.join(__dir__, '..', '..', 'web', 'views', 'render_html.erb'))
|
116
122
|
|
117
123
|
ERB.new(page_erb_template).result(ERBBinding.new(view).get_binding do
|
118
124
|
render_template(view)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module TRMNLPreview
|
4
|
+
module CustomFilters
|
5
|
+
def number_with_delimiter(*args)
|
6
|
+
ActiveSupport::NumberHelper.number_to_delimited(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def number_to_currency(*args)
|
10
|
+
ActiveSupport::NumberHelper.number_to_currency(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'ferrum'
|
2
|
+
require 'mini_magick'
|
3
|
+
require 'puppeteer-ruby'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module TRMNLPreview
|
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/trmnl_preview.gemspec
CHANGED
@@ -31,14 +31,25 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = ["trmnlp"]
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
|
34
|
+
|
35
|
+
# Web server
|
35
36
|
spec.add_dependency "sinatra", "~> 4.1"
|
36
37
|
spec.add_dependency "rackup", "~> 2.2"
|
37
38
|
spec.add_dependency "puma", "~> 6.5"
|
39
|
+
spec.add_dependency "faye-websocket", "~> 0.11.3"
|
40
|
+
|
41
|
+
# HTML rendering
|
38
42
|
spec.add_dependency "liquid", "~> 5.6"
|
43
|
+
spec.add_dependency "activesupport", "~> 8.0"
|
44
|
+
|
45
|
+
# BMP rendering
|
46
|
+
spec.add_dependency "ferrum", "~> 0.16"
|
47
|
+
spec.add_dependency 'puppeteer-ruby', '~> 0.45.6'
|
48
|
+
spec.add_dependency 'mini_magick', '~> 4.12.0'
|
49
|
+
|
50
|
+
# Utilities
|
39
51
|
spec.add_dependency "toml-rb", "~> 3.0"
|
40
52
|
spec.add_dependency "filewatcher", "~> 2.1"
|
41
|
-
spec.add_dependency "faye-websocket", "~> 0.11.3"
|
42
53
|
|
43
54
|
# For more information and examples about making a new gem, check out our
|
44
55
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -0,0 +1,98 @@
|
|
1
|
+
body {
|
2
|
+
font-family: sans-serif;
|
3
|
+
margin: 10px;
|
4
|
+
}
|
5
|
+
|
6
|
+
main {
|
7
|
+
display: flex;
|
8
|
+
flex-direction: column;
|
9
|
+
width: fit-content;
|
10
|
+
}
|
11
|
+
|
12
|
+
menu {
|
13
|
+
padding: 0;
|
14
|
+
margin: 0;
|
15
|
+
display: flex;
|
16
|
+
justify-content: space-between;
|
17
|
+
}
|
18
|
+
|
19
|
+
menu a {
|
20
|
+
padding: 0.5em 1em;
|
21
|
+
background: #ddd;
|
22
|
+
border-radius: 0.5em;
|
23
|
+
display: inline-block;
|
24
|
+
text-decoration: none;
|
25
|
+
color: black;
|
26
|
+
}
|
27
|
+
|
28
|
+
menu a:hover {
|
29
|
+
background: #ccc;
|
30
|
+
}
|
31
|
+
|
32
|
+
menu a.active {
|
33
|
+
background: #333;
|
34
|
+
color: white;
|
35
|
+
}
|
36
|
+
|
37
|
+
.case {
|
38
|
+
width: 1000px;
|
39
|
+
height: 680px;
|
40
|
+
position: relative;
|
41
|
+
}
|
42
|
+
|
43
|
+
iframe {
|
44
|
+
position: absolute;
|
45
|
+
border: none;
|
46
|
+
left: 98px;
|
47
|
+
top: 65px;
|
48
|
+
filter: grayscale(100%);
|
49
|
+
}
|
50
|
+
|
51
|
+
.case .case-overlay {
|
52
|
+
position: absolute;
|
53
|
+
width: 100%;
|
54
|
+
height: 100%;
|
55
|
+
top: 0;
|
56
|
+
left: 0;
|
57
|
+
background-size: cover;
|
58
|
+
mix-blend-mode: multiply;
|
59
|
+
pointer-events: none;
|
60
|
+
}
|
61
|
+
|
62
|
+
.case--none .case-overlay {
|
63
|
+
background: none;
|
64
|
+
}
|
65
|
+
|
66
|
+
.case--white .case-overlay {
|
67
|
+
background-image: url("white-case.jpg");
|
68
|
+
}
|
69
|
+
|
70
|
+
.case--black .case-overlay {
|
71
|
+
background-image: url("black-case.jpg");
|
72
|
+
}
|
73
|
+
|
74
|
+
.case--clear .case-overlay {
|
75
|
+
background-image: url("clear-case.jpg");
|
76
|
+
}
|
77
|
+
|
78
|
+
.case--none iframe {
|
79
|
+
border: 1px solid black;
|
80
|
+
}
|
81
|
+
|
82
|
+
.spinner {
|
83
|
+
width: 22px;
|
84
|
+
height: 22px;
|
85
|
+
border: 5px solid #f3f3f3; /* Light gray */
|
86
|
+
border-top: 5px solid #3498db; /* Blue */
|
87
|
+
border-radius: 50%;
|
88
|
+
animation: spin 1s linear infinite;
|
89
|
+
}
|
90
|
+
|
91
|
+
@keyframes spin {
|
92
|
+
0% {
|
93
|
+
transform: rotate(0deg);
|
94
|
+
}
|
95
|
+
100% {
|
96
|
+
transform: rotate(360deg);
|
97
|
+
}
|
98
|
+
}
|
data/web/public/index.js
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
const trmnlp = {};
|
2
|
+
|
3
|
+
trmnlp.connectLiveRender = function () {
|
4
|
+
const ws = new WebSocket("/live_reload");
|
5
|
+
|
6
|
+
ws.onopen = function () {
|
7
|
+
console.log("Connected to live reload socket");
|
8
|
+
};
|
9
|
+
|
10
|
+
ws.onmessage = function (msg) {
|
11
|
+
if (msg.data === "reload") {
|
12
|
+
trmnlp.setIframeSrc(trmnlp.iframe.src);
|
13
|
+
}
|
14
|
+
};
|
15
|
+
|
16
|
+
ws.onclose = function () {
|
17
|
+
console.log("Reconnecting to live reload socket...");
|
18
|
+
setTimeout(trmnlp.connectLiveRender, 1000);
|
19
|
+
};
|
20
|
+
};
|
21
|
+
|
22
|
+
trmnlp.setCaseImage = function () {
|
23
|
+
const value = trmnlp.caseSelect.value;
|
24
|
+
document.querySelector(".case").className = `case case--${value}`;
|
25
|
+
localStorage.setItem("trmnlp-case", value);
|
26
|
+
};
|
27
|
+
|
28
|
+
trmnlp.setPreviewFormat = function () {
|
29
|
+
const value = trmnlp.formatSelect.value;
|
30
|
+
localStorage.setItem("trmnlp-format", value);
|
31
|
+
|
32
|
+
trmnlp.setIframeSrc(`/render/${trmnlp.view}.${value}`);
|
33
|
+
};
|
34
|
+
|
35
|
+
trmnlp.setIframeSrc = function (src) {
|
36
|
+
document.querySelector(".spinner").style.display = "inline-block";
|
37
|
+
trmnlp.iframe.src = src;
|
38
|
+
};
|
39
|
+
|
40
|
+
document.addEventListener("DOMContentLoaded", function () {
|
41
|
+
trmnlp.view = document.querySelector("meta[name='trmnl-view']").content;
|
42
|
+
trmnlp.iframe = document.querySelector("iframe");
|
43
|
+
trmnlp.caseSelect = document.querySelector(".select-case");
|
44
|
+
trmnlp.formatSelect = document.querySelector(".select-format");
|
45
|
+
trmnlp.isLiveReloadEnabled =
|
46
|
+
document.querySelector("meta[name='live-reload']").content === "true";
|
47
|
+
|
48
|
+
if (trmnlp.isLiveReloadEnabled) {
|
49
|
+
trmnlp.connectLiveRender();
|
50
|
+
}
|
51
|
+
|
52
|
+
const caseValue = localStorage.getItem("trmnlp-case") || "black";
|
53
|
+
const formatValue = localStorage.getItem("trmnlp-format") || "html";
|
54
|
+
|
55
|
+
trmnlp.caseSelect.value = caseValue;
|
56
|
+
trmnlp.caseSelect.addEventListener("change", () => {
|
57
|
+
trmnlp.setCaseImage();
|
58
|
+
});
|
59
|
+
|
60
|
+
trmnlp.formatSelect.value = formatValue;
|
61
|
+
trmnlp.formatSelect.addEventListener("change", () => {
|
62
|
+
trmnlp.setPreviewFormat();
|
63
|
+
});
|
64
|
+
|
65
|
+
trmnlp.iframe.addEventListener("load", () => {
|
66
|
+
document.querySelector(".spinner").style.display = "none";
|
67
|
+
});
|
68
|
+
|
69
|
+
trmnlp.setCaseImage();
|
70
|
+
trmnlp.setPreviewFormat();
|
71
|
+
});
|
data/web/views/index.erb
CHANGED
@@ -3,85 +3,13 @@
|
|
3
3
|
<head>
|
4
4
|
<meta charset="UTF-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<meta name="trmnl-view" content="<%= @view %>">
|
7
|
+
<meta name="live-reload" content="<%= @live_reload %>">
|
8
|
+
|
6
9
|
<title>TRMNL Preview</title>
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
margin: 10px;
|
11
|
-
}
|
12
|
-
main {
|
13
|
-
display: flex;
|
14
|
-
flex-direction: column;
|
15
|
-
width: fit-content;
|
16
|
-
}
|
17
|
-
menu {
|
18
|
-
padding: 0;
|
19
|
-
margin: 0;
|
20
|
-
display: flex;
|
21
|
-
justify-content: space-between;
|
22
|
-
}
|
23
|
-
menu a {
|
24
|
-
padding: 0.5em 1em;
|
25
|
-
background: #ddd;
|
26
|
-
border-radius: 0.5em;
|
27
|
-
display: inline-block;
|
28
|
-
text-decoration: none;
|
29
|
-
color: black;
|
30
|
-
}
|
31
|
-
menu a:hover {
|
32
|
-
background: #ccc;
|
33
|
-
}
|
34
|
-
menu a.active {
|
35
|
-
background: #333;
|
36
|
-
color: white;
|
37
|
-
}
|
38
|
-
.case {
|
39
|
-
width: 1000px;
|
40
|
-
height: 680px;
|
41
|
-
position: relative;
|
42
|
-
}
|
43
|
-
iframe {
|
44
|
-
position: absolute;
|
45
|
-
border: none;
|
46
|
-
left: 98px;
|
47
|
-
top: 65px;
|
48
|
-
filter: grayscale(100%);
|
49
|
-
}
|
50
|
-
.case .case-overlay {
|
51
|
-
position: absolute;
|
52
|
-
width: 100%;
|
53
|
-
height: 100%;
|
54
|
-
top: 0;
|
55
|
-
left: 0;
|
56
|
-
background-size: cover;
|
57
|
-
mix-blend-mode: multiply;
|
58
|
-
pointer-events: none;
|
59
|
-
}
|
60
|
-
.case--none .case-overlay { background: none; }
|
61
|
-
.case--white .case-overlay { background-image: url('white-case.jpg') }
|
62
|
-
.case--black .case-overlay { background-image: url('black-case.jpg') }
|
63
|
-
.case--clear .case-overlay { background-image: url('clear-case.jpg') }
|
64
|
-
.case--none iframe {
|
65
|
-
border: 1px solid black;
|
66
|
-
}
|
67
|
-
</style>
|
68
|
-
<script>
|
69
|
-
function updateCase() {
|
70
|
-
const value = document.querySelector('.select-case').value;
|
71
|
-
document.querySelector('.case').className = `case case--${value}`;
|
72
|
-
localStorage.setItem('trmnlp-case', value);
|
73
|
-
}
|
74
|
-
|
75
|
-
document.addEventListener('DOMContentLoaded', () => {
|
76
|
-
const caseValue = localStorage.getItem('trmnlp-case') || 'black';
|
77
|
-
|
78
|
-
const select = document.querySelector('.select-case');
|
79
|
-
select.value = caseValue;
|
80
|
-
select.addEventListener('change', () => { updateCase() });
|
81
|
-
|
82
|
-
updateCase();
|
83
|
-
});
|
84
|
-
</script>
|
10
|
+
|
11
|
+
<link rel="stylesheet" href="/index.css">
|
12
|
+
<script src="/index.js"></script>
|
85
13
|
</head>
|
86
14
|
<body>
|
87
15
|
<main>
|
@@ -92,7 +20,12 @@
|
|
92
20
|
<a class="<%= 'active' if @view == 'half_vertical' %>" href="/half_vertical">Half Vertical</a>
|
93
21
|
<a class="<%= 'active' if @view == 'quadrant' %>" href="/quadrant">Quadrant</a>
|
94
22
|
</div>
|
95
|
-
<div>
|
23
|
+
<div style="display: flex; gap: 0.3em;">
|
24
|
+
<div class="spinner" style="display: none;"></div>
|
25
|
+
<select class="select-format">
|
26
|
+
<option value="bmp">BMP</option>
|
27
|
+
<option value="html" selected>HTML</option>
|
28
|
+
</select>
|
96
29
|
<select class="select-case">
|
97
30
|
<option value="none">None</option>
|
98
31
|
<option value="white">White</option>
|
@@ -105,7 +38,7 @@
|
|
105
38
|
</menu>
|
106
39
|
|
107
40
|
<div class="case case--black">
|
108
|
-
<iframe
|
41
|
+
<iframe width="800" height="480"></iframe>
|
109
42
|
<div class="case-overlay"></div>
|
110
43
|
</div>
|
111
44
|
</main>
|
@@ -9,11 +9,6 @@
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet">
|
11
11
|
<!-- End Inter font -->
|
12
|
-
|
13
|
-
<% if @live_render %>
|
14
|
-
<meta name="trmnl-view" content="<%= @view %>">
|
15
|
-
<script src="/live-render.js"></script>
|
16
|
-
<% end %>
|
17
12
|
</head>
|
18
13
|
<body class="environment trmnl">
|
19
14
|
<div class="screen">
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trmnl_preview
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rockwell Schrock
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-01-
|
10
|
+
date: 2025-01-08 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: sinatra
|
@@ -51,6 +51,20 @@ dependencies:
|
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '6.5'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: faye-websocket
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.11.3
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.11.3
|
54
68
|
- !ruby/object:Gem::Dependency
|
55
69
|
name: liquid
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,47 +80,89 @@ dependencies:
|
|
66
80
|
- !ruby/object:Gem::Version
|
67
81
|
version: '5.6'
|
68
82
|
- !ruby/object:Gem::Dependency
|
69
|
-
name:
|
83
|
+
name: activesupport
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
71
85
|
requirements:
|
72
86
|
- - "~>"
|
73
87
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
88
|
+
version: '8.0'
|
75
89
|
type: :runtime
|
76
90
|
prerelease: false
|
77
91
|
version_requirements: !ruby/object:Gem::Requirement
|
78
92
|
requirements:
|
79
93
|
- - "~>"
|
80
94
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
95
|
+
version: '8.0'
|
82
96
|
- !ruby/object:Gem::Dependency
|
83
|
-
name:
|
97
|
+
name: ferrum
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
85
99
|
requirements:
|
86
100
|
- - "~>"
|
87
101
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
102
|
+
version: '0.16'
|
89
103
|
type: :runtime
|
90
104
|
prerelease: false
|
91
105
|
version_requirements: !ruby/object:Gem::Requirement
|
92
106
|
requirements:
|
93
107
|
- - "~>"
|
94
108
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
109
|
+
version: '0.16'
|
96
110
|
- !ruby/object:Gem::Dependency
|
97
|
-
name:
|
111
|
+
name: puppeteer-ruby
|
98
112
|
requirement: !ruby/object:Gem::Requirement
|
99
113
|
requirements:
|
100
114
|
- - "~>"
|
101
115
|
- !ruby/object:Gem::Version
|
102
|
-
version: 0.
|
116
|
+
version: 0.45.6
|
103
117
|
type: :runtime
|
104
118
|
prerelease: false
|
105
119
|
version_requirements: !ruby/object:Gem::Requirement
|
106
120
|
requirements:
|
107
121
|
- - "~>"
|
108
122
|
- !ruby/object:Gem::Version
|
109
|
-
version: 0.
|
123
|
+
version: 0.45.6
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: mini_magick
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 4.12.0
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 4.12.0
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: toml-rb
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '3.0'
|
145
|
+
type: :runtime
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '3.0'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: filewatcher
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '2.1'
|
159
|
+
type: :runtime
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '2.1'
|
110
166
|
description: Automatically rebuild and preview TRNML plugins in multiple views
|
111
167
|
email:
|
112
168
|
- rockwell@schrock.me
|
@@ -127,16 +183,19 @@ files:
|
|
127
183
|
- lib/trmnl_preview/cmd/build.rb
|
128
184
|
- lib/trmnl_preview/cmd/serve.rb
|
129
185
|
- lib/trmnl_preview/cmd/usage.rb
|
186
|
+
- lib/trmnl_preview/cmd/version.rb
|
130
187
|
- lib/trmnl_preview/context.rb
|
131
|
-
- lib/trmnl_preview/
|
188
|
+
- lib/trmnl_preview/custom_filters.rb
|
189
|
+
- lib/trmnl_preview/screen_generator.rb
|
132
190
|
- lib/trmnl_preview/version.rb
|
133
191
|
- trmnl_preview.gemspec
|
134
192
|
- web/public/black-case.jpg
|
135
193
|
- web/public/clear-case.jpg
|
136
|
-
- web/public/
|
194
|
+
- web/public/index.css
|
195
|
+
- web/public/index.js
|
137
196
|
- web/public/white-case.jpg
|
138
197
|
- web/views/index.erb
|
139
|
-
- web/views/
|
198
|
+
- web/views/render_html.erb
|
140
199
|
homepage: https://github.com/schrockwell/trmnl_preview
|
141
200
|
licenses:
|
142
201
|
- MIT
|
data/web/public/live-render.js
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
live_render = {};
|
2
|
-
|
3
|
-
live_render.connect = function () {
|
4
|
-
const view = document.querySelector("meta[name='trmnl-view']").content;
|
5
|
-
const ws = new WebSocket("/live_render/" + view);
|
6
|
-
|
7
|
-
ws.onopen = function () {
|
8
|
-
console.log("Connected to live push server");
|
9
|
-
};
|
10
|
-
|
11
|
-
ws.onmessage = function (msg) {
|
12
|
-
document.querySelector(".view").innerHTML = msg.data;
|
13
|
-
};
|
14
|
-
|
15
|
-
ws.onclose = function () {
|
16
|
-
console.log("Reconnecting to live push server...");
|
17
|
-
setTimeout(live_render.connect, 1000);
|
18
|
-
};
|
19
|
-
};
|
20
|
-
|
21
|
-
document.addEventListener("DOMContentLoaded", function () {
|
22
|
-
live_render.connect();
|
23
|
-
});
|