scarpe 0.4.0 → 0.5.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/rules/commit-style-preferences.mdc +72 -0
  3. data/.cursor/rules/component_context.mdc +82 -0
  4. data/.cursor/rules/debug-failed-tests.mdc +100 -0
  5. data/.cursor/rules/display_service_context.mdc +80 -0
  6. data/.cursor/rules/event_handling_context.mdc +100 -0
  7. data/.cursor/rules/git-pager-handling.mdc +64 -0
  8. data/.cursor/rules/lacci-context.mdc +52 -0
  9. data/.cursor/rules/scarpe_design_context.mdc +46 -0
  10. data/.cursor/rules/shoes_compatibility_context.mdc +75 -0
  11. data/.cursor/rules/timeout_context.mdc +78 -0
  12. data/.cursor/rules/update_lacci_and_wv.mdc +8 -0
  13. data/.cursor/rules/what_is_scarpe.mdc +22 -0
  14. data/.cursor/rules/writing-new-rules.mdc +73 -0
  15. data/CHANGELOG.md +10 -1
  16. data/CLAUDE.md +223 -0
  17. data/Gemfile +0 -1
  18. data/Gemfile.lock +78 -58
  19. data/README.md +4 -7
  20. data/Rakefile +17 -25
  21. data/docs/SCARPE_FEATURES.md +38 -0
  22. data/docs/_config.yml +13 -0
  23. data/docs/calzini_components_and_updates.md +78 -0
  24. data/docs/display_service_separation.md +39 -0
  25. data/docs/documentation.md +43 -0
  26. data/docs/event_loops.md +66 -0
  27. data/docs/image.png +0 -0
  28. data/docs/index.md +118 -0
  29. data/docs/lacci.md +121 -0
  30. data/docs/scarpe_shoes_incompatibilities.md +71 -0
  31. data/docs/shoes_and_display_events.md +55 -0
  32. data/docs/shoes_implementations.md +79 -0
  33. data/docs/static/manual.md +5 -0
  34. data/docs/static/scarpe-logo.png +0 -0
  35. data/docs/timeouts_and_handlers.md +66 -0
  36. data/docs/web_archaeology.md +76 -0
  37. data/examples/background_with_image.rb +14 -5
  38. data/examples/bloopsaphone/working/feepogram.rb +1 -1
  39. data/examples/bloopsaphone/working/le_dance_des_rubis.rb +135 -0
  40. data/examples/bloopsaphone/working/pixel_dreams_in_ruby.rb +131 -0
  41. data/examples/bloopsaphone/working/type_rebellion.rb +157 -0
  42. data/examples/border.rb +1 -1
  43. data/examples/internal_link_navigation.rb +19 -0
  44. data/examples/page_navigation_single_app.rb +42 -0
  45. data/examples/shoes_subclass_app.rb +25 -0
  46. data/examples/url_routing_example.rb +67 -0
  47. data/lacci/Gemfile +0 -2
  48. data/lacci/Gemfile.lock +4 -32
  49. data/lacci/lacci.gemspec +1 -1
  50. data/lacci/lib/lacci/version.rb +1 -1
  51. data/lacci/lib/scarpe/niente/app.rb +12 -1
  52. data/lacci/lib/scarpe/niente/shoes_spec.rb +4 -5
  53. data/lacci/lib/scarpe/niente.rb +1 -0
  54. data/lacci/lib/shoes/app.rb +166 -61
  55. data/lacci/lib/shoes/constants.rb +1 -0
  56. data/lacci/lib/shoes/drawable.rb +35 -19
  57. data/lacci/lib/shoes/drawables/arc.rb +2 -2
  58. data/lacci/lib/shoes/drawables/arrow.rb +2 -2
  59. data/lacci/lib/shoes/drawables/border.rb +1 -1
  60. data/lacci/lib/shoes/drawables/button.rb +1 -1
  61. data/lacci/lib/shoes/drawables/edit_line.rb +1 -1
  62. data/lacci/lib/shoes/drawables/flow.rb +1 -1
  63. data/lacci/lib/shoes/drawables/line.rb +2 -2
  64. data/lacci/lib/shoes/drawables/link.rb +11 -1
  65. data/lacci/lib/shoes/drawables/oval.rb +2 -2
  66. data/lacci/lib/shoes/drawables/rect.rb +2 -2
  67. data/lacci/lib/shoes/drawables/shape.rb +2 -2
  68. data/lacci/lib/shoes/drawables/slot.rb +5 -3
  69. data/lacci/lib/shoes/drawables/stack.rb +1 -1
  70. data/lacci/lib/shoes/drawables/star.rb +1 -1
  71. data/lacci/lib/shoes/drawables/widget.rb +1 -1
  72. data/lacci/lib/shoes.rb +94 -17
  73. data/lacci/test/test_margin_helper.rb +1 -1
  74. data/lacci/test/test_niente_test_infra.rb +14 -0
  75. data/lacci/test/test_shoes_errors.rb +15 -13
  76. data/lib/scarpe/assets.rb +2 -1
  77. data/lib/scarpe/shoes_spec.rb +2 -1
  78. data/lib/scarpe/version.rb +1 -1
  79. data/lib/scarpe/wv/edit_line.rb +2 -2
  80. data/lib/scarpe/wv.rb +8 -1
  81. data/scarpe-components/Gemfile +0 -2
  82. data/scarpe-components/Gemfile.lock +4 -34
  83. data/scarpe-components/lib/scarpe/components/calzini/misc.rb +10 -2
  84. data/scarpe-components/lib/scarpe/components/calzini/para.rb +6 -1
  85. data/scarpe-components/lib/scarpe/components/calzini/slots.rb +2 -0
  86. data/scarpe-components/lib/scarpe/components/port_helpers.rb +30 -0
  87. data/scarpe-components/lib/scarpe/components/version.rb +1 -1
  88. data/scarpe-components/scarpe-components.gemspec +1 -1
  89. data/scarpe-components/test/test_port_helpers.rb +12 -0
  90. metadata +60 -22
  91. data/.rubocop.yml +0 -94
@@ -62,8 +62,10 @@ class Shoes::Slot < Shoes::Drawable
62
62
  # Look up the Shoes drawable and create it. But first set
63
63
  # this slot as the current one so that draw context
64
64
  # is handled properly.
65
- Shoes::App.instance.with_slot(self) do
66
- instance = klass.new(*args, **kwargs, &block)
65
+ @app.with_slot(self) do
66
+ Shoes::Drawable.with_current_app(self.app) do
67
+ instance = klass.new(*args, **kwargs, &block)
68
+ end
67
69
  end
68
70
 
69
71
  instance
@@ -173,6 +175,6 @@ class Shoes::Slot < Shoes::Drawable
173
175
  raise(Shoes::Errors::InvalidAttributeValueError, "append requires a block!") unless block_given?
174
176
  raise(Shoes::Errors::InvalidAttributeValueError, "Don't append to something that isn't a slot!") unless self.is_a?(Shoes::Slot)
175
177
 
176
- Shoes::App.instance.with_slot(self, &block)
178
+ @app.with_slot(self, &block)
177
179
  end
178
180
  end
@@ -15,7 +15,7 @@ class Shoes
15
15
 
16
16
  # Create the display-side drawable *before* running the block.
17
17
  # Then child drawables have a parent to add themselves to.
18
- Shoes::App.instance.with_slot(self, &block) if block_given?
18
+ @app.with_slot(self, &block) if block_given?
19
19
  end
20
20
  end
21
21
  end
@@ -19,7 +19,7 @@ class Shoes
19
19
  def initialize(*args, **kwargs)
20
20
  super
21
21
 
22
- @draw_context = Shoes::App.instance.current_draw_context
22
+ @draw_context = @app.current_draw_context
23
23
 
24
24
  create_display_drawable
25
25
  end
@@ -67,7 +67,7 @@ class Shoes::Widget < Shoes::Slot
67
67
  __widget_initialize(*args, **kwargs, &block)
68
68
 
69
69
  # Do Widgets do this?
70
- Shoes::App.instance.with_slot(self, &block) if block
70
+ @app.with_slot(self, &block) if block
71
71
  end
72
72
  @midway_through_adding_initialize = false
73
73
  end
data/lacci/lib/shoes.rb CHANGED
@@ -7,47 +7,47 @@
7
7
  # to handle the DSL and command-line parts of Shoes without knowing anything about how the
8
8
  # display side works at all.
9
9
 
10
- if RUBY_VERSION[0..2] < "3.2"
11
- Shoes::Log.logger("Shoes").error("Lacci (Scarpe, Shoes) requires Ruby 3.2 or higher!")
10
+ if RUBY_VERSION[0..2] < '3.2'
11
+ Shoes::Log.logger('Shoes').error('Lacci (Scarpe, Shoes) requires Ruby 3.2 or higher!')
12
12
  exit(-1)
13
13
  end
14
14
 
15
15
  class Shoes; end
16
16
  class Shoes::Error < StandardError; end
17
- require_relative "shoes/errors"
17
+ require_relative 'shoes/errors'
18
18
 
19
- require_relative "shoes/constants"
20
- require_relative "shoes/ruby_extensions"
19
+ require_relative 'shoes/constants'
20
+ require_relative 'shoes/ruby_extensions'
21
21
 
22
22
  # Shoes adds some top-level methods and constants that can be used everywhere. Kernel is where they go.
23
23
  module Kernel
24
24
  include Shoes::Constants
25
25
  end
26
26
 
27
- require_relative "shoes/display_service"
27
+ require_relative 'shoes/display_service'
28
28
 
29
29
  # Pre-declare classes that get referenced outside their own require file
30
30
  class Shoes::Drawable < Shoes::Linkable; end
31
31
  class Shoes::Slot < Shoes::Drawable; end
32
32
  class Shoes::Widget < Shoes::Slot; end
33
33
 
34
- require_relative "shoes/log"
35
- require_relative "shoes/colors"
34
+ require_relative 'shoes/log'
35
+ require_relative 'shoes/colors'
36
36
 
37
- require_relative "shoes/builtins"
37
+ require_relative 'shoes/builtins'
38
38
 
39
- require_relative "shoes/background"
39
+ require_relative 'shoes/background'
40
40
 
41
- require_relative "shoes/drawable"
42
- require_relative "shoes/app"
43
- require_relative "shoes/drawables"
41
+ require_relative 'shoes/drawable'
42
+ require_relative 'shoes/app'
43
+ require_relative 'shoes/drawables'
44
44
 
45
- require_relative "shoes/download"
45
+ require_relative 'shoes/download'
46
46
 
47
47
  # No easy way to tell at this point whether
48
48
  # we will later load Shoes-Spec code, e.g.
49
49
  # by running a segmented app with test code.
50
- require_relative "shoes-spec"
50
+ require_relative 'shoes-spec'
51
51
 
52
52
  # The module containing Shoes in all its glory.
53
53
  # Shoes is a platform-independent GUI library, designed to create
@@ -55,6 +55,40 @@ require_relative "shoes-spec"
55
55
  #
56
56
  class Shoes
57
57
  class << self
58
+ attr_accessor :APPS
59
+
60
+ # Track the most recently defined Shoes subclass for the inheritance pattern
61
+ # e.g., class Book < Shoes; end; Shoes.app
62
+ attr_accessor :pending_app_class
63
+
64
+ # When someone does `class MyApp < Shoes`, track it
65
+ def inherited(subclass)
66
+ # Only track direct subclasses of Shoes, not Shoes::App, Shoes::Drawable, etc.
67
+ # Those have their own inheritance tracking
68
+ if self == ::Shoes
69
+ Shoes.pending_app_class = subclass
70
+ end
71
+ super
72
+ end
73
+
74
+ # Class-level url method for defining routes in Shoes subclasses
75
+ # e.g., class Book < Shoes; url '/', :index; end
76
+ def url(path, method_name)
77
+ @class_routes ||= {}
78
+ if path.is_a?(String) && path.include?('(')
79
+ # Convert string patterns like '/page/(\d+)' to regex
80
+ regex = Regexp.new("^#{path.gsub(/\(.*?\)/, '(.*?)')}$")
81
+ @class_routes[regex] = method_name
82
+ else
83
+ @class_routes[path] = method_name
84
+ end
85
+ end
86
+
87
+ # Get the routes defined on this class
88
+ def class_routes
89
+ @class_routes ||= {}
90
+ end
91
+
58
92
  # Creates a Shoes app with a new window. The block parameter is used to create
59
93
  # drawables and set up handlers. Arguments are passed to Shoes::App.new internally.
60
94
  #
@@ -78,7 +112,7 @@ class Shoes
78
112
  # @return [void]
79
113
  # @see Shoes::App#new
80
114
  def app(
81
- title: "Shoes!",
115
+ title: 'Shoes!',
82
116
  width: 480,
83
117
  height: 420,
84
118
  resizable: true,
@@ -87,6 +121,47 @@ class Shoes
87
121
  )
88
122
  f = [features].flatten # Make sure this is a list, not a single symbol
89
123
  app = Shoes::App.new(title:, width:, height:, resizable:, features: f, &app_code_body)
124
+
125
+ # If there's a pending Shoes subclass (e.g., class Book < Shoes), use it
126
+ if Shoes.pending_app_class
127
+ subclass = Shoes.pending_app_class
128
+ Shoes.pending_app_class = nil # Clear it so it doesn't affect future apps
129
+
130
+ # Include the subclass as a module to get its instance methods
131
+ # This works because we're extending the singleton class
132
+ methods_to_copy = subclass.instance_methods(false)
133
+
134
+ methods_to_copy.each do |method_name|
135
+ # Get source location and use eval to redefine - but that's fragile
136
+ # Instead, let's use a delegation pattern with the app as context
137
+
138
+ # Read the method's arity and create a proper wrapper
139
+ um = subclass.instance_method(method_name)
140
+
141
+ # Define a wrapper that will eval the original method body in app's context
142
+ # This is a bit hacky but works: we store the subclass and call via instance_eval
143
+ app.define_singleton_method(method_name) do |*args, &block|
144
+ # Create a temporary subclass instance that delegates to app for Shoes methods
145
+ temp = subclass.allocate
146
+ temp.instance_variable_set(:@__shoes_app__, self)
147
+
148
+ # Define method_missing on the temp to delegate Shoes DSL calls to the app
149
+ temp.define_singleton_method(:method_missing) do |name, *a, **kw, &b|
150
+ @__shoes_app__.send(name, *a, **kw, &b)
151
+ end
152
+ temp.define_singleton_method(:respond_to_missing?) { |*| true }
153
+
154
+ # Call the original method on temp (which delegates DSL calls to app)
155
+ temp.send(method_name, *args, &block)
156
+ end
157
+ end
158
+
159
+ # Copy routes from the subclass to the app
160
+ subclass.class_routes.each do |path, method_name|
161
+ app.url(path, method_name)
162
+ end
163
+ end
164
+
90
165
  app.init
91
166
  app.run
92
167
  nil
@@ -125,7 +200,7 @@ class Shoes
125
200
  proc do |path|
126
201
  load path
127
202
  true
128
- end,
203
+ end
129
204
  ]
130
205
  end
131
206
 
@@ -145,4 +220,6 @@ class Shoes
145
220
  @file_loaders = loaders
146
221
  end
147
222
  end
223
+
224
+ Shoes.APPS ||= []
148
225
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- load "lacci/lib/shoes/margin_helper.rb"
3
+ require_relative "../lib/shoes/margin_helper"
4
4
 
5
5
 
6
6
  class TestMarginHelper < Minitest::Test
@@ -23,4 +23,18 @@ class TestNienteTestInfra < NienteTest
23
23
  raise "ERROR!"
24
24
  SHOES_SPEC
25
25
  end
26
+
27
+ def test_multi_app_find
28
+ run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
29
+ Shoes.app do
30
+ @b = button "OK"
31
+ end
32
+ Shoes.app do
33
+ @b2 = button "Nope"
34
+ end
35
+ SHOES_APP
36
+ assert_equal "OK", button("@b").text
37
+ assert_equal "Nope", button("@b2").text
38
+ SHOES_SPEC
39
+ end
26
40
  end
@@ -17,19 +17,21 @@ class TestShoesErrors < NienteTest
17
17
  SHOES_SPEC
18
18
  end
19
19
 
20
- def test_too_many_instances_error
21
- run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
22
- $ruby_main = self
23
- Shoes.app do
24
- end
25
- SHOES_APP
26
- assert_raises Shoes::Errors::TooManyInstancesError do
27
- $ruby_main.instance_eval do
28
- Shoes.app {}
29
- end
30
- end
31
- SHOES_SPEC
32
- end
20
+ # Niente can no longer test the TooManyInstancesError because it now
21
+ # supports multiple Shoes apps.
22
+ #def test_too_many_instances_error
23
+ # run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
24
+ # $ruby_main = self
25
+ # Shoes.app do
26
+ # end
27
+ # SHOES_APP
28
+ # assert_raises Shoes::Errors::TooManyInstancesError do
29
+ # $ruby_main.instance_eval do
30
+ # Shoes.app {}
31
+ # end
32
+ # end
33
+ # SHOES_SPEC
34
+ #end
33
35
 
34
36
  def test_drawables_found_errors
35
37
  run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
data/lib/scarpe/assets.rb CHANGED
@@ -6,7 +6,8 @@ module Scarpe::Webview
6
6
  def self.asset_server
7
7
  return @asset_server if @asset_server
8
8
 
9
- @asset_server = Scarpe::Components::AssetServer.new app_dir: Shoes::App.instance.dir
9
+ # A Scarpe Webview application can have only a single Shoes::App instance.
10
+ @asset_server = Scarpe::Components::AssetServer.new app_dir: Shoes.APPS[0].dir
10
11
 
11
12
  # at_exit doesn't work reliably under webview. Give this a try.
12
13
  ::Scarpe::Webview::DisplayService.instance.control_interface.on_event(:shutdown) do
@@ -107,7 +107,8 @@ module Scarpe::ShoesSpecTest
107
107
  finder_name = drawable_class.dsl_name
108
108
 
109
109
  define_method(finder_name) do |*args|
110
- app = Shoes::App.instance
110
+ # Scarpe-Webview only supports a single Shoes::App instance
111
+ app = Shoes.APPS[0]
111
112
 
112
113
  drawables = app.find_drawables_by(drawable_class, *args)
113
114
  raise Shoes::Errors::MultipleDrawablesFoundError, "Found more than one #{finder_name} matching #{args.inspect}!" if drawables.size > 1
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scarpe
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Scarpe::Webview
4
4
  class EditLine < Drawable
5
- attr_reader :text, :width, :stroke, :font, :tooltip
5
+ attr_reader :text, :width, :stroke, :font, :tooltip, :secret
6
6
 
7
7
  def initialize(properties)
8
8
  super
@@ -15,7 +15,7 @@ module Scarpe::Webview
15
15
  send_self_event(new_text, event_name: "hover")
16
16
  end
17
17
  end
18
-
18
+
19
19
  def properties_changed(changes)
20
20
  t = changes.delete("text")
21
21
  if t
data/lib/scarpe/wv.rb CHANGED
@@ -7,7 +7,14 @@
7
7
  require "securerandom"
8
8
  require "json"
9
9
 
10
- require "bloops"
10
+ # Bloops is optional - only required if you want sound support
11
+ # Install with: gem install bloops (requires portaudio)
12
+ begin
13
+ require "bloops"
14
+ rescue LoadError
15
+ # Bloops not installed - sound features will not be available
16
+ end
17
+
11
18
  require "scarpe/components/html" # HTML renderer
12
19
  require "scarpe/components/modular_logger"
13
20
  require "scarpe/components/promises"
@@ -20,6 +20,4 @@ end
20
20
 
21
21
  group :development do
22
22
  gem "debug"
23
- gem "rubocop", "~> 1.21"
24
- gem "rubocop-shopify"
25
23
  end
@@ -1,20 +1,18 @@
1
1
  PATH
2
2
  remote: ../lacci
3
3
  specs:
4
- lacci (0.3.0)
5
- scarpe-components
4
+ lacci (0.4.0)
5
+ scarpe-components (~> 0.4.0)
6
6
 
7
7
  PATH
8
8
  remote: .
9
9
  specs:
10
- scarpe-components (0.3.0)
10
+ scarpe-components (0.4.0)
11
11
 
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
15
  ansi (1.5.0)
16
- ast (2.4.2)
17
- base64 (0.1.1)
18
16
  builder (3.2.4)
19
17
  debug (1.8.0)
20
18
  irb (>= 1.5.0)
@@ -23,51 +21,25 @@ GEM
23
21
  irb (1.8.0)
24
22
  rdoc (~> 6.5)
25
23
  reline (>= 0.3.6)
26
- json (2.6.3)
27
- language_server-protocol (3.17.0.3)
28
24
  minitest (5.19.0)
29
25
  minitest-reporters (1.6.1)
30
26
  ansi
31
27
  builder
32
28
  minitest (>= 5.0)
33
29
  ruby-progressbar
34
- parallel (1.23.0)
35
- parser (3.2.2.3)
36
- ast (~> 2.4.1)
37
- racc
38
30
  psych (5.1.0)
39
31
  stringio
40
- racc (1.7.1)
41
- rainbow (3.1.1)
42
32
  rake (13.0.6)
43
33
  rdoc (6.5.0)
44
34
  psych (>= 4.0.0)
45
- regexp_parser (2.8.1)
46
35
  reline (0.3.8)
47
36
  io-console (~> 0.5)
48
- rexml (3.2.6)
49
- rubocop (1.56.2)
50
- base64 (~> 0.1.1)
51
- json (~> 2.3)
52
- language_server-protocol (>= 3.17.0)
53
- parallel (~> 1.10)
54
- parser (>= 3.2.2.3)
55
- rainbow (>= 2.2.2, < 4.0)
56
- regexp_parser (>= 1.8, < 3.0)
57
- rexml (>= 3.2.5, < 4.0)
58
- rubocop-ast (>= 1.28.1, < 2.0)
59
- ruby-progressbar (~> 1.7)
60
- unicode-display_width (>= 2.4.0, < 3.0)
61
- rubocop-ast (1.29.0)
62
- parser (>= 3.2.1.0)
63
- rubocop-shopify (2.14.0)
64
- rubocop (~> 1.51)
65
37
  ruby-progressbar (1.13.0)
66
38
  stringio (3.0.8)
67
- unicode-display_width (2.4.2)
68
39
  webrick (1.8.1)
69
40
 
70
41
  PLATFORMS
42
+ arm64-darwin-21
71
43
  x86_64-darwin-22
72
44
 
73
45
  DEPENDENCIES
@@ -76,8 +48,6 @@ DEPENDENCIES
76
48
  minitest (~> 5.0)
77
49
  minitest-reporters
78
50
  rake (~> 13.0)
79
- rubocop (~> 1.21)
80
- rubocop-shopify
81
51
  scarpe-components!
82
52
  webrick
83
53
 
@@ -22,9 +22,17 @@ module Scarpe::Components::Calzini
22
22
 
23
23
  def edit_line_element(props)
24
24
  oninput = handler_js_code("change", "this.value")
25
-
25
+
26
26
  HTML.render do |h|
27
- h.input(id: html_id, oninput: oninput, onmouseover: handler_js_code("hover"), value: props["text"], style: edit_line_style(props),title: props["tooltip"])
27
+ h.input(
28
+ id: html_id,
29
+ type: props["secret"] ? :password : :text,
30
+ oninput: oninput,
31
+ onmouseover: handler_js_code("hover"),
32
+ value: props["text"],
33
+ style: edit_line_style(props),
34
+ title: props["tooltip"]
35
+ )
28
36
  end
29
37
  end
30
38
 
@@ -220,9 +220,14 @@ module Scarpe::Components::Calzini
220
220
  private
221
221
 
222
222
  def text_drawable_attrs(props)
223
+ click_value = props["click"]
224
+ # Internal routes (starting with /) should use onclick, not href
225
+ # External URLs use href for normal browser navigation
226
+ is_internal_route = click_value.is_a?(String) && click_value.start_with?("/")
227
+
223
228
  {
224
229
  # These properties will normally only be set by link()
225
- href: props["click"],
230
+ href: is_internal_route ? nil : click_value,
226
231
  onclick: props["has_block"] ? handler_js_code("click") : nil,
227
232
  }.compact
228
233
  end
@@ -100,6 +100,8 @@ module Scarpe::Components::Calzini
100
100
  "linear-gradient(45deg, #{bc.first}, #{bc.last})"
101
101
  when ->(value) { File.exist?(value) }
102
102
  "url(data:image/png;base64,#{encode_file_to_base64(bc)})"
103
+ when ->(value) { valid_url?(value) }
104
+ "url(#{bc})"
103
105
  else
104
106
  bc
105
107
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Scarpe::Components
6
+ module PortHelpers
7
+ MAX_SERVER_STARTUP_WAIT = 5.0
8
+
9
+ def port_working?(ip, port_num)
10
+ begin
11
+ TCPSocket.new(ip, port_num)
12
+ rescue Errno::ECONNREFUSED
13
+ return false
14
+ end
15
+ return true
16
+ end
17
+
18
+ def wait_until_port_working(ip, port_num, max_wait: MAX_SERVER_STARTUP_WAIT)
19
+ t_start = Time.now
20
+ loop do
21
+ if Time.now - t_start > max_wait
22
+ raise "Server on port #{port_num} didn't start up in time!"
23
+ end
24
+
25
+ sleep 0.1
26
+ return if port_working?(ip, port_num)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Scarpe
4
4
  module Components
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17
17
 
18
18
  spec.metadata["homepage_uri"] = spec.homepage
19
- spec.metadata["source_code_uri"] = "https://github.com/scarpe-team/scarpe"
19
+ #spec.metadata["source_code_uri"] = "https://github.com/scarpe-team/scarpe"
20
20
  spec.metadata["changelog_uri"] = "https://github.com/scarpe-team/scarpe/blob/main/CHANGELOG.md"
21
21
 
22
22
  # Specify which files should be added to the gem when it is released.
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ require "scarpe/components/port_helpers"
6
+ class TestPortHelpers < Minitest::Test
7
+ include Scarpe::Components::PortHelpers
8
+
9
+ def test_port_finder
10
+ assert_equal false, port_working?("127.0.0.1", 9832), "Port 9832 should be unused!"
11
+ end
12
+ end