trmnl_preview 0.5.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cee09b6deb0fc063e5358d6d636536513dc31cba86f0fe4e71ee4d2e1d3a7a5
4
- data.tar.gz: 7b421d7ae1ace73b2e7370191e853946a857278f4f17332ace859bcf9581822a
3
+ metadata.gz: c67d7a534ea6adba08ab5b46d40f60ec8e505123136916ba98e43991c6877f58
4
+ data.tar.gz: 5a22288cb32a76b2b8cb8d8163a38c964736e6fd7c067da6f76f9fe8c7401ec1
5
5
  SHA512:
6
- metadata.gz: 5d97a1e0f9c461796aa141b88d7c4c2826e2693764c5cdba9013260162b9dad16220b47bd49b36b2f8cbbaeb2c985d1851f3780b33326bd9f518f1d9604774eb
7
- data.tar.gz: 16a38e4c92c20db294e80e6c729a3e7f12cdf5667785d80baabeb4ab93720a56b02a3346cb6efdae01d5d715b1919eab1d7fb4f34f40ffee2c7e96ce80158a69
6
+ metadata.gz: 9df016907e67d1cdc0b771299162b769c83b2bb2c7d9ab4b0e5bd77cf8db3f6a8f15db93ad4a00810e0fd224cb2e3dbee1876c9fe416854f26f239704a28537a
7
+ data.tar.gz: bc4e3d5c46fc64d25a3791c77c0e43af5a84875c2ad2853742e3ba31cf7a5bbdef5a548d92a4bab4b27b048e089eae4e712cd99eec43ad1ee0902f27e39a364c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2
4
+
5
+ - Added `time_zone` project config option, which is injected into `trmnl.user` variables
6
+ - Fixed time zone to always be UTC, matching usetrmnl.com servers (#38)
7
+
3
8
  ## 0.5.1
4
9
 
5
10
  - Fixed `trmnl init`
data/README.md CHANGED
@@ -22,6 +22,7 @@ This is the structure of a plugin project:
22
22
  ├── half_horizontal.liquid
23
23
  ├── half_vertical.liquid
24
24
  ├── quadrant.liquid
25
+ ├── shared.liquid
25
26
  └── settings.yml
26
27
  ```
27
28
 
@@ -94,6 +95,9 @@ watch:
94
95
  custom_fields:
95
96
  station: "{{ env.ICAO }}" # interpolate $IACO environment variable
96
97
 
98
+ # Time zone IANA identifier to inject into trmnl.user; see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
99
+ time_zone: America/New_York
100
+
97
101
  # override variables
98
102
  variables:
99
103
  trmnl:
data/bin/trmnlp CHANGED
@@ -1,9 +1,12 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
- require "thor"
3
+ require 'thor'
4
4
 
5
5
  require_relative '../lib/trmnlp/cli'
6
6
 
7
+ # Force UTC to match usetrmnl.com server time zone
8
+ ENV['TZ'] = 'UTC'
9
+
7
10
  begin
8
11
  TRMNLP::CLI.start
9
12
  rescue TRMNLP::Error => e
@@ -0,0 +1,36 @@
1
+ require 'action_view'
2
+ require 'singleton'
3
+
4
+ module Markup
5
+ module CustomLiquidFilters
6
+ class ActionViewHelpers
7
+ include Singleton
8
+ include ActionView::Helpers
9
+ end
10
+
11
+ def number_with_delimiter(number, delimiter = ',', separator = ',')
12
+ ActionViewHelpers.instance.number_with_delimiter(number, delimiter:, separator:)
13
+ end
14
+
15
+ def number_to_currency(number, unit = '$', delimiter = ',', separator = '.')
16
+ ActionViewHelpers.instance.number_to_currency(number, unit: unit, delimiter:, separator:)
17
+ end
18
+
19
+ def l_word(word, locale)
20
+ I18n.t("custom_plugins.#{word}", locale: locale)
21
+ end
22
+
23
+ def l_date(date, format, locale = 'en')
24
+ format = format.to_sym unless format.include?('%')
25
+ I18n.l(date.to_datetime, :format => format, locale: locale)
26
+ end
27
+
28
+ def pluralize(singular, count)
29
+ ActionViewHelpers.instance.pluralize(count, singular)
30
+ end
31
+
32
+ def json(obj)
33
+ JSON.generate(obj)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ module Markup
2
+ # This in-memory "file system" is the backing storage for custom templates defined {% template [name] %} tags.
3
+ class InlineTemplatesFileSystem < Liquid::BlankFileSystem
4
+ def initialize
5
+ super
6
+ @templates = {}
7
+ end
8
+
9
+ # called by Markup::LiquidTemplateTag to save users' custom shared templates via our custom {% template %} tag
10
+ def register(name, body)
11
+ @templates[name] = body
12
+ end
13
+
14
+ # called by Liquid::Template for {% render 'foo' %} when rendering screen markup
15
+ def read_template_file(name)
16
+ @templates[name] || raise(Liquid::FileSystemError, "Template not found: #{name}")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # get all files in the current directory as this file
2
+ Pathname.new(__dir__).glob('*.rb').each { |file| require file }
3
+
4
+ module Markup
5
+ # A very thin wrapper around Liquid::Template with TRMNL-specific functionality.
6
+ class Template < Liquid::Template
7
+ def self.parse(*)
8
+ template = super
9
+
10
+ # set up a temporary in-memory file system for custom user templates, via the magic :file_system register
11
+ # which will override the default file system
12
+ template.registers[:file_system] = InlineTemplatesFileSystem.new
13
+
14
+ template
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ module Markup
2
+ # The {% template [name] %} tag block is used in conjunction with InlineTemplatesFileSystem to allow users to define
3
+ # custom templates within the context of the current Liquid template. Generally speaking, they will define their own
4
+ # templates in the "shared" markup content, which is prepended to the individual screen templates before rendering.
5
+ class TemplateTag < Liquid::Block
6
+ NAME_REGEX = %r{\A[a-zA-Z0-9_/]+\z}
7
+
8
+ def initialize(tag_name, markup, options)
9
+ super
10
+ @name = markup.strip
11
+ end
12
+
13
+ def parse(tokens)
14
+ @body = ""
15
+ while (token = tokens.shift)
16
+ break if token.strip == "{% endtemplate %}"
17
+
18
+ @body << token
19
+ end
20
+ end
21
+
22
+ def render(context)
23
+ unless @name =~ NAME_REGEX
24
+ return "Liquid error: invalid template name #{@name.inspect} - template names must contain only letters, numbers, underscores, and slashes"
25
+ end
26
+
27
+ context.registers[:file_system].register(@name, @body.strip)
28
+ ''
29
+ end
30
+ end
31
+ end
@@ -13,6 +13,7 @@ module TRMNLP
13
13
 
14
14
  api_key = prompt("API Key: ")
15
15
  raise Error, "API key cannot be empty" if api_key.empty?
16
+ raise Error, "Invalid API key; did you copy it from the right place?" unless api_key.start_with?("user_")
16
17
 
17
18
  config.app.api_key = api_key
18
19
  config.app.save
@@ -36,6 +36,8 @@ module TRMNLP
36
36
  Liquid::Template.parse(value).render(custom_fields_with_env)
37
37
  end
38
38
 
39
+ def time_zone = @config['time_zone'] || 'UTC'
40
+
39
41
  private
40
42
 
41
43
  # for interpolating ENV vars into custom_fields
@@ -1,3 +1,4 @@
1
+ require 'active_support/time'
1
2
  require 'erb'
2
3
  require 'faraday'
3
4
  require 'filewatcher'
@@ -5,8 +6,8 @@ require 'json'
5
6
  require 'liquid'
6
7
 
7
8
  require_relative 'config'
8
- require_relative 'custom_filters'
9
9
  require_relative 'paths'
10
+ require_relative '../markup/template'
10
11
 
11
12
  module TRMNLP
12
13
  class Context
@@ -121,7 +122,14 @@ module TRMNLP
121
122
  template_path = paths.template(view)
122
123
  return "Missing template: #{template_path}" unless template_path.exist?
123
124
 
124
- user_template = Liquid::Template.parse(template_path.read, environment: liquid_environment)
125
+ shared_template_path = paths.shared_template
126
+ if shared_template_path.exist?
127
+ full_markup = shared_template_path.read + template_path.read
128
+ else
129
+ full_markup = template_path.read
130
+ end
131
+
132
+ user_template = Markup::Template.parse(full_markup, environment: liquid_environment)
125
133
  user_template.render(user_data)
126
134
  rescue StandardError => e
127
135
  e.message
@@ -159,6 +167,11 @@ module TRMNLP
159
167
  end
160
168
 
161
169
  def base_trmnl_data
170
+ tz = ActiveSupport::TimeZone.find_tzinfo(config.project.time_zone)
171
+ time_zone_iana = tz.name
172
+ time_zone_name = ActiveSupport::TimeZone::MAPPING.invert[time_zone_iana] || time_zone_iana
173
+ utc_offset = tz.utc_offset
174
+
162
175
  {
163
176
  'trmnl' => {
164
177
  'user' => {
@@ -166,9 +179,9 @@ module TRMNLP
166
179
  'first_name' => 'first_name',
167
180
  'last_name' => 'last_name',
168
181
  'locale' => 'en',
169
- 'time_zone' => 'Eastern Time (US & Canada)',
170
- 'time_zone_iana' => 'America/New_York',
171
- 'utc_offset' => -14400
182
+ 'time_zone' => time_zone_name,
183
+ 'time_zone_iana' => time_zone_iana,
184
+ 'utc_offset' => utc_offset,
172
185
  },
173
186
  'device' => {
174
187
  'friendly_id' => 'ABC123',
@@ -194,7 +207,8 @@ module TRMNLP
194
207
 
195
208
  def liquid_environment
196
209
  @liquid_environment ||= Liquid::Environment.build do |env|
197
- env.register_filter(CustomFilters)
210
+ env.register_filter(Markup::CustomLiquidFilters)
211
+ env.register_tag('template', Markup::TemplateTag)
198
212
 
199
213
  config.project.user_filters.each do |module_name, relative_path|
200
214
  require paths.root_dir.join(relative_path)
data/lib/trmnlp/paths.rb CHANGED
@@ -36,6 +36,8 @@ module TRMNLP
36
36
  def plugin_config = src_dir.join('settings.yml')
37
37
 
38
38
  def template(view) = src_dir.join("#{view}.liquid")
39
+
40
+ def shared_template = template('shared')
39
41
 
40
42
  def app_config = app_config_dir.join('config.yml')
41
43
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLP
4
- VERSION = "0.5.1".freeze
4
+ VERSION = "0.5.3".freeze
5
5
  end
@@ -45,6 +45,7 @@ Gem::Specification.new do |spec|
45
45
  # HTML rendering
46
46
  spec.add_dependency "liquid", "~> 5.6"
47
47
  spec.add_dependency "activesupport", "~> 8.0"
48
+ spec.add_dependency "actionview", "~> 8.0"
48
49
 
49
50
  # BMP rendering
50
51
  spec.add_dependency "ferrum", "~> 0.16"
@@ -59,6 +60,7 @@ Gem::Specification.new do |spec|
59
60
  spec.add_dependency "rubyzip", "~> 2.3.0"
60
61
  spec.add_dependency "thor", "~> 1.3"
61
62
  spec.add_dependency "oj", "~> 3.16.9"
63
+ spec.add_dependency "tzinfo-data", "~> 1.2025"
62
64
 
63
65
  # For more information and examples about making a new gem, check out our
64
66
  # guide at: https://bundler.io/guides/creating_gem.html
data/web/public/index.css CHANGED
@@ -34,51 +34,6 @@ menu a.active {
34
34
  color: white;
35
35
  }
36
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
37
  .spinner {
83
38
  width: 22px;
84
39
  height: 22px;
data/web/public/index.js CHANGED
@@ -11,7 +11,7 @@ trmnlp.connectLiveRender = function () {
11
11
  const payload = JSON.parse(msg.data);
12
12
 
13
13
  if (payload.type === "reload") {
14
- trmnlp.setIframeSrc(trmnlp.iframe.src);
14
+ trmnlp.setFrameSrc(trmnlp.frame._src);
15
15
  trmnlp.userData.textContent = JSON.stringify(payload.user_data, null, 2);
16
16
  }
17
17
  };
@@ -22,9 +22,9 @@ trmnlp.connectLiveRender = function () {
22
22
  };
23
23
  };
24
24
 
25
- trmnlp.setCaseImage = function () {
25
+ trmnlp.setFrameColor = function () {
26
26
  const value = trmnlp.caseSelect.value;
27
- document.querySelector(".case").className = `case case--${value}`;
27
+ document.querySelector("trmnl-frame").setAttribute("color", value);
28
28
  localStorage.setItem("trmnlp-case", value);
29
29
  };
30
30
 
@@ -32,17 +32,17 @@ trmnlp.setPreviewFormat = function () {
32
32
  const value = trmnlp.formatSelect.value;
33
33
  localStorage.setItem("trmnlp-format", value);
34
34
 
35
- trmnlp.setIframeSrc(`/render/${trmnlp.view}.${value}`);
35
+ trmnlp.setFrameSrc(`/render/${trmnlp.view}.${value}`);
36
36
  };
37
37
 
38
- trmnlp.setIframeSrc = function (src) {
38
+ trmnlp.setFrameSrc = function (src) {
39
39
  document.querySelector(".spinner").style.display = "inline-block";
40
- trmnlp.iframe.src = src;
40
+ trmnlp.frame.setSrc(src);
41
41
  };
42
42
 
43
43
  document.addEventListener("DOMContentLoaded", function () {
44
44
  trmnlp.view = document.querySelector("meta[name='trmnl-view']").content;
45
- trmnlp.iframe = document.querySelector("iframe");
45
+ trmnlp.frame = document.querySelector("trmnl-frame");
46
46
  trmnlp.caseSelect = document.querySelector(".select-case");
47
47
  trmnlp.formatSelect = document.querySelector(".select-format");
48
48
  trmnlp.userData = document.getElementById("user-data");
@@ -58,7 +58,7 @@ document.addEventListener("DOMContentLoaded", function () {
58
58
 
59
59
  trmnlp.caseSelect.value = caseValue;
60
60
  trmnlp.caseSelect.addEventListener("change", () => {
61
- trmnlp.setCaseImage();
61
+ trmnlp.setFrameColor();
62
62
  });
63
63
 
64
64
  trmnlp.formatSelect.value = formatValue;
@@ -66,10 +66,15 @@ document.addEventListener("DOMContentLoaded", function () {
66
66
  trmnlp.setPreviewFormat();
67
67
  });
68
68
 
69
- trmnlp.iframe.addEventListener("load", () => {
69
+ trmnlp.frame._iframe.addEventListener("load", () => {
70
70
  document.querySelector(".spinner").style.display = "none";
71
+
72
+ // On page load, trmnl-frame loads "about:blank", so wait for that to load
73
+ // before updating the src to the preview.
74
+ if (trmnlp.frame._src === null) {
75
+ trmnlp.setPreviewFormat();
76
+ }
71
77
  });
72
78
 
73
- trmnlp.setCaseImage();
74
- trmnlp.setPreviewFormat();
79
+ trmnlp.setFrameColor();
75
80
  });