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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a39d955b3dbcc869cde5e0210671a94dfa7af0ea1403fb71c883534d85b6eac
4
- data.tar.gz: f0159db40a383ce3149071fd4826bdb9035abab379d442e8a93012a35f6cce30
3
+ metadata.gz: e965687c88e23b15e8727f9b871b6a2529bc28c388fa2e8733fb5034915ec405
4
+ data.tar.gz: c1299d3ce7b27a13be79d0018535c8f1b64171f7154d9dd1f425eeacbcbc0129
5
5
  SHA512:
6
- metadata.gz: cccd5ba2af16c057717ae4f0acb6843fad3cd9a7f305109817c5d195f278dd1102c39a20cbf21abbbae0e18395b41ac8e5b314f5c10ae763503cc3e877f1403b
7
- data.tar.gz: 19eb007c4d077a3078cde135ca200873f5d050abe167306b878b3f7374e052c282d3cb348e806486ce62fa0d6c0913efb1b4feaf100e213ad16d6e5167426ed9
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
- # trmnl_preview
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 locally as HTML, leveraging the [TRMNL Design System](https://usetrmnl.com/framework). This server does NOT generate a rendered BMP file. Hence, this is just a _preview_ of the final rendered dashboard.
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
- Ruby 3.x is required. In the plugin repository:
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
- bundle add trmnl_preview # Creates Gemfile and Gemfile.lock
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
@@ -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
- @live_render_clients = VIEWS.each_with_object({}) { |view, hash| hash[view] = [] }
27
+ @live_reload_clients = []
27
28
  @context.on_view_change do |view|
28
- @live_render_clients[view].each do |ws|
29
- ws.send(@context.render_template(view))
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 '/live_render/:view' do
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
- @live_render_clients[view] << ws
48
+ @live_reload_clients << ws
49
49
  end
50
50
 
51
51
  ws.on(:close) do |event|
52
- @live_render_clients[view].delete(ws)
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
- @live_render = @context.live_render
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
@@ -5,6 +5,7 @@ Usage:
5
5
 
6
6
  Commands (-h for command-specific help):
7
7
 
8
- serve Start the TRMNL Preview server
9
8
  build Generate static HTML files
9
+ serve Start the TRMNL Preview server
10
+ version Print the version number
10
11
  USAGE
@@ -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 'liquid_filters'
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', 'render_view.erb'))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLPreview
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
@@ -31,14 +31,25 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = ["trmnlp"]
32
32
  spec.require_paths = ["lib"]
33
33
 
34
- # Uncomment to register a new dependency of your gem
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
+ }
@@ -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
- <style>
8
- body {
9
- font-family: sans-serif;
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 src="/render/<%= @view %>" width="800" height="480"></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.1
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-06 00:00:00.000000000 Z
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: toml-rb
83
+ name: activesupport
70
84
  requirement: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - "~>"
73
87
  - !ruby/object:Gem::Version
74
- version: '3.0'
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: '3.0'
95
+ version: '8.0'
82
96
  - !ruby/object:Gem::Dependency
83
- name: filewatcher
97
+ name: ferrum
84
98
  requirement: !ruby/object:Gem::Requirement
85
99
  requirements:
86
100
  - - "~>"
87
101
  - !ruby/object:Gem::Version
88
- version: '2.1'
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: '2.1'
109
+ version: '0.16'
96
110
  - !ruby/object:Gem::Dependency
97
- name: faye-websocket
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.11.3
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.11.3
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/liquid_filters.rb
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/live-render.js
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/render_view.erb
198
+ - web/views/render_html.erb
140
199
  homepage: https://github.com/schrockwell/trmnl_preview
141
200
  licenses:
142
201
  - MIT
@@ -1,8 +0,0 @@
1
- module TRMNLPreview
2
- module LiquidFilters
3
- def number_with_delimiter(number)
4
- # TODO: Replace with ActiveSupport's number_with_delimiter
5
- number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
6
- end
7
- end
8
- end
@@ -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
- });