isomorfeus-preact 10.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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +62 -0
  3. data/lib/browser/delegate_native.rb +70 -0
  4. data/lib/browser/element.rb +176 -0
  5. data/lib/browser/element/canvas.rb +17 -0
  6. data/lib/browser/element/media.rb +78 -0
  7. data/lib/browser/event.rb +92 -0
  8. data/lib/browser/event_target.rb +39 -0
  9. data/lib/browser/file_list.rb +125 -0
  10. data/lib/browser/iterable.rb +15 -0
  11. data/lib/isomorfeus-preact.rb +109 -0
  12. data/lib/isomorfeus/preact/config.rb +189 -0
  13. data/lib/isomorfeus/preact/memcached_component_cache.rb +19 -0
  14. data/lib/isomorfeus/preact/redis_component_cache.rb +19 -0
  15. data/lib/isomorfeus/preact/thread_local_component_cache.rb +17 -0
  16. data/lib/isomorfeus/preact_view_helper.rb +188 -0
  17. data/lib/isomorfeus/props/validate_hash_proxy.rb +186 -0
  18. data/lib/isomorfeus/props/validator.rb +159 -0
  19. data/lib/isomorfeus/top_level.rb +101 -0
  20. data/lib/isomorfeus/top_level_ssr.rb +27 -0
  21. data/lib/isomorfeus_preact/lucid_app/api.rb +22 -0
  22. data/lib/isomorfeus_preact/lucid_app/base.rb +7 -0
  23. data/lib/isomorfeus_preact/lucid_app/mixin.rb +16 -0
  24. data/lib/isomorfeus_preact/lucid_app/native_component_constructor.rb +91 -0
  25. data/lib/isomorfeus_preact/lucid_component/api.rb +68 -0
  26. data/lib/isomorfeus_preact/lucid_component/app_store_proxy.rb +37 -0
  27. data/lib/isomorfeus_preact/lucid_component/base.rb +7 -0
  28. data/lib/isomorfeus_preact/lucid_component/class_store_proxy.rb +44 -0
  29. data/lib/isomorfeus_preact/lucid_component/initializer.rb +14 -0
  30. data/lib/isomorfeus_preact/lucid_component/instance_store_proxy.rb +44 -0
  31. data/lib/isomorfeus_preact/lucid_component/mixin.rb +15 -0
  32. data/lib/isomorfeus_preact/lucid_component/native_component_constructor.rb +84 -0
  33. data/lib/isomorfeus_preact/lucid_component/styles_api.rb +31 -0
  34. data/lib/isomorfeus_preact/lucid_component/styles_wrapper.rb +40 -0
  35. data/lib/isomorfeus_preact/lucid_func/base.rb +7 -0
  36. data/lib/isomorfeus_preact/lucid_func/initializer.rb +11 -0
  37. data/lib/isomorfeus_preact/lucid_func/mixin.rb +12 -0
  38. data/lib/isomorfeus_preact/lucid_func/native_component_constructor.rb +55 -0
  39. data/lib/isomorfeus_preact/preact/function_component/api.rb +123 -0
  40. data/lib/isomorfeus_preact/preact/function_component/base.rb +9 -0
  41. data/lib/isomorfeus_preact/preact/function_component/initializer.rb +10 -0
  42. data/lib/isomorfeus_preact/preact/function_component/mixin.rb +12 -0
  43. data/lib/isomorfeus_preact/preact/function_component/native_component_constructor.rb +48 -0
  44. data/lib/lucid_app/context.rb +24 -0
  45. data/lib/lucid_prop_declaration/mixin.rb +126 -0
  46. data/lib/preact.rb +309 -0
  47. data/lib/preact/component/api.rb +124 -0
  48. data/lib/preact/component/base.rb +9 -0
  49. data/lib/preact/component/callbacks.rb +102 -0
  50. data/lib/preact/component/elements.rb +64 -0
  51. data/lib/preact/component/initializer.rb +11 -0
  52. data/lib/preact/component/mixin.rb +15 -0
  53. data/lib/preact/component/native_component_constructor.rb +65 -0
  54. data/lib/preact/component/params.rb +18 -0
  55. data/lib/preact/component/props.rb +55 -0
  56. data/lib/preact/component/resolution.rb +97 -0
  57. data/lib/preact/component/state.rb +58 -0
  58. data/lib/preact/context_wrapper.rb +46 -0
  59. data/lib/preact/native_constant_wrapper.rb +29 -0
  60. data/lib/preact/options.rb +98 -0
  61. data/lib/preact/ref.rb +17 -0
  62. data/lib/preact/version.rb +3 -0
  63. metadata +301 -0
@@ -0,0 +1,39 @@
1
+ module Browser
2
+ module EventTarget
3
+ # Add the block as a handler for the specified event name. Will use either
4
+ # `addEventListener` or `addListener` if they exist.
5
+ #
6
+ # @param event_name [String] the name of the event
7
+ # @return [Proc] the block to pass to `off` to remove this handler
8
+ # @yieldparam event [Browser::Event] the event object
9
+ def on event_name, &block
10
+ wrapper = proc { |event| block.call Event.new(event) }
11
+
12
+ if `#@native.addEventListener !== undefined`
13
+ `#@native.addEventListener(event_name, wrapper)`
14
+ elsif `#@native.addListener !== undefined`
15
+ `#@native.addListener(event_name, wrapper)`
16
+ else
17
+ warn "[Browser] Not entirely sure how to add an event listener to #{self}"
18
+ end
19
+
20
+ wrapper
21
+ end
22
+
23
+ # Remove an event handler
24
+ #
25
+ # @param event_name [String] the name of the event
26
+ # @block the handler to remove, as returned from `on`
27
+ def off event_name, &block
28
+ if `#@native.removeEventListener !== undefined`
29
+ `#@native.removeEventListener(event_name, block)`
30
+ elsif `#@native.removeListener !== undefined`
31
+ `#@native.removeListener(event_name, block)`
32
+ else
33
+ warn "[Browser] Not entirely sure how to remove an event listener from #{self}"
34
+ end
35
+
36
+ nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,125 @@
1
+ module Browser
2
+ class FileList
3
+ include Enumerable
4
+
5
+ # @param native [JS] the native FileList object to wrap
6
+ def initialize native
7
+ @native = `#{native} || []`
8
+ @files = length.times.each_with_object([]) { |index, array|
9
+ array[index] = File.new(`#@native[index]`)
10
+ }
11
+ end
12
+
13
+ # @param index [Integer] the index of the file in the list
14
+ # @return [Browser::FileList::File] the file at the specified index
15
+ def [] index
16
+ @files[index]
17
+ end
18
+
19
+ # @return [Integer] the number of files in this list
20
+ def length
21
+ `#@native.length`
22
+ end
23
+ alias size length
24
+
25
+ # Call the given block for each file in the list
26
+ #
27
+ # @yieldparam file [Browser::FileList::File]
28
+ def each &block
29
+ @files.each do |file|
30
+ block.call file
31
+ end
32
+ end
33
+
34
+ # Convert this FileList into an array
35
+ def to_a
36
+ @files.dup # Don't return a value that can mutate our internal state
37
+ end
38
+ alias to_ary to_a
39
+
40
+ # @return [String] a string representation of this FileList
41
+ def to_s
42
+ @files.to_s
43
+ end
44
+
45
+ # An individual item in a FileList
46
+ class File
47
+ attr_reader :data
48
+
49
+ # @param native [JS] the native File object to wrap
50
+ def initialize native
51
+ @native = native
52
+ @data = nil
53
+ end
54
+
55
+ # @return [String] the filename
56
+ def name
57
+ `#@native.name`
58
+ end
59
+
60
+ # @return [Integer] the size of this file on disk
61
+ def size
62
+ `#@native.size`
63
+ end
64
+
65
+ # @return [String] the MIME type of the file, detected by the browser
66
+ def type
67
+ `#@native.type`
68
+ end
69
+
70
+ # @return [Time] the timestamp of the file
71
+ def last_modified
72
+ `#@native.lastModifiedDate`
73
+ end
74
+
75
+ # Read the file from disk into memory
76
+ #
77
+ # @return [Promise] a promise that resolves when finished loading and
78
+ # rejects if an error occurs while loading.
79
+ def read
80
+ promise = Promise.new
81
+ reader = FileReader.new
82
+ reader.on :load do
83
+ result = reader.result
84
+
85
+ @data = result
86
+ promise.resolve result
87
+ end
88
+
89
+ reader.on :error do
90
+ promise.reject reader.result
91
+ end
92
+
93
+ reader.read_as_binary_string self
94
+
95
+ promise
96
+ end
97
+
98
+ # Convert to the native object
99
+ #
100
+ # @return [JS.HTMLElement] the underlying native element
101
+ def to_n
102
+ @native
103
+ end
104
+
105
+ # The object that reads the file from disk.
106
+ #
107
+ # @api private
108
+ class FileReader
109
+ include EventTarget
110
+
111
+ def initialize
112
+ @native = `new FileReader()`
113
+ end
114
+
115
+ def result
116
+ `#@native.result`
117
+ end
118
+
119
+ def read_as_binary_string file
120
+ `#@native.readAsBinaryString(#{file.to_n})`
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,15 @@
1
+ module Browser
2
+ class Iterable
3
+ include Enumerable
4
+
5
+ def initialize js_iterable
6
+ @js_iterable = js_iterable
7
+ end
8
+
9
+ def each
10
+ `#@js_iterable.length`.times do |i|
11
+ yield `#@js_iterable[i]`
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'isomorfeus-redux'
3
+ require 'active_support/core_ext/string'
4
+ require 'zeitwerk'
5
+
6
+ if on_browser?
7
+ require 'browser/event'
8
+ require 'browser/event_target'
9
+ require 'browser/delegate_native'
10
+ require 'browser/element'
11
+ end
12
+
13
+ require 'isomorfeus/preact/config'
14
+
15
+ # allow mounting of components
16
+ if on_browser?
17
+ require 'isomorfeus/top_level'
18
+ else
19
+ require 'isomorfeus/top_level_ssr'
20
+ end
21
+
22
+ # preact
23
+ require 'preact/version'
24
+ require 'preact'
25
+ require 'preact/ref'
26
+
27
+ # props
28
+ require 'isomorfeus/props/validate_hash_proxy'
29
+ require 'isomorfeus/props/validator'
30
+ require 'lucid_prop_declaration/mixin'
31
+ require 'preact/component/props'
32
+
33
+ # HTML Elements and Fragment support
34
+ require 'preact/component/elements'
35
+
36
+ # Preact Wrappers
37
+ require 'preact/context_wrapper'
38
+ require 'preact/native_constant_wrapper'
39
+
40
+ # Preact::Component
41
+ require 'preact/component/api'
42
+ require 'preact/component/callbacks'
43
+ require 'preact/component/initializer'
44
+ require 'preact/component/native_component_constructor'
45
+ require 'preact/component/state'
46
+ require 'preact/component/params'
47
+ require 'preact/component/resolution'
48
+ require 'preact/component/mixin'
49
+ require 'preact/component/base'
50
+
51
+ # init LucidApplicationContext (Store Provider and Consumer)
52
+ require 'lucid_app/context'
53
+ LucidApp::Context.create_application_context
54
+
55
+ class Object
56
+ include Preact::Component::Resolution
57
+ end
58
+
59
+ Isomorfeus.zeitwerk = Zeitwerk::Loader.new
60
+
61
+ Isomorfeus.zeitwerk.push_dir('isomorfeus_preact')
62
+ require_tree 'isomorfeus_preact', :autoload
63
+
64
+ Isomorfeus.zeitwerk.push_dir('components')
65
+ else
66
+ require 'uri'
67
+ require 'oj'
68
+ require 'opal'
69
+ require 'opal-activesupport'
70
+ require 'opal-zeitwerk'
71
+ require 'opal-webpack-loader'
72
+ require 'isomorfeus-redux'
73
+ require 'isomorfeus-speednode'
74
+ require 'preact/version'
75
+ require 'isomorfeus/preact/config'
76
+
77
+ # props
78
+ require 'isomorfeus/props/validate_hash_proxy'
79
+ require 'isomorfeus/props/validator'
80
+ require 'lucid_prop_declaration/mixin'
81
+
82
+ Isomorfeus.env = ENV['RACK_ENV']
83
+
84
+ if Isomorfeus.development?
85
+ require 'net/http'
86
+ Isomorfeus.ssr_hot_asset_url = 'http://localhost:3036/assets/'
87
+ end
88
+
89
+ Isomorfeus.server_side_rendering = true
90
+
91
+ # caches
92
+ require 'isomorfeus/preact/thread_local_component_cache'
93
+ require 'isomorfeus/preact/memcached_component_cache'
94
+ require 'isomorfeus/preact/redis_component_cache'
95
+ require 'isomorfeus/preact_view_helper'
96
+
97
+ Isomorfeus.component_cache_init do
98
+ Isomorfeus::ThreadLocalComponentCache.new
99
+ end
100
+
101
+ Opal.append_path(__dir__.untaint)
102
+
103
+ require 'concurrent'
104
+ require 'zeitwerk'
105
+
106
+ Isomorfeus.zeitwerk = Zeitwerk::Loader.new
107
+ Isomorfeus.zeitwerk_lock = Concurrent::ReentrantReadWriteLock.new if Isomorfeus.development?
108
+ # nothing to push_dir to zeitwerk here, as components are available only within browser/SSR
109
+ end
@@ -0,0 +1,189 @@
1
+ module Isomorfeus
2
+ if RUBY_ENGINE == 'opal'
3
+ class << self
4
+ attr_accessor :current_user_sid
5
+ attr_accessor :initial_state_fetched
6
+ attr_accessor :top_component
7
+ attr_accessor :ssr_response_status
8
+ attr_reader :initialized
9
+ attr_reader :env
10
+ attr_accessor :zeitwerk
11
+
12
+ def init
13
+ return if initialized
14
+ @initialized = true
15
+ Isomorfeus.init_store
16
+ execute_init_classes
17
+ end
18
+
19
+ def force_init!
20
+ unless Isomorfeus.initial_state_fetched
21
+ Isomorfeus.initial_state_fetched = true
22
+ Redux::Store.preloaded_state = Isomorfeus.store.get_state
23
+ end
24
+ Isomorfeus.force_init_store!
25
+ execute_init_classes
26
+ end
27
+
28
+ def add_client_init_class_name(init_class_name)
29
+ client_init_class_names << init_class_name
30
+ end
31
+
32
+ def add_client_init_after_store_class_name(init_class_name)
33
+ client_init_after_store_class_names << init_class_name
34
+ end
35
+
36
+ def add_client_option(key, value = nil)
37
+ self.class.attr_accessor(key)
38
+ self.send("#{key}=", value)
39
+ end
40
+
41
+ # only used for SSR
42
+ def cached_component_classes
43
+ @cached_component_classes ||= {}
44
+ end
45
+
46
+ # only used for SSR
47
+ def cached_component_class(class_name)
48
+ return cached_component_classes[class_name] if cached_component_classes.key?(class_name)
49
+ cached_component_classes[class_name] = "::#{class_name}".constantize
50
+ end
51
+
52
+ def execute_init_classes
53
+ client_init_class_names.each do |constant|
54
+ constant.constantize.send(:init)
55
+ end
56
+ end
57
+
58
+ def execute_init_after_store_classes
59
+ client_init_after_store_class_names.each do |constant|
60
+ constant.constantize.send(:init)
61
+ end
62
+ end
63
+
64
+ def env=(env_string)
65
+ @env = env_string ? env_string : 'development'
66
+ @development = (@env == 'development') ? true : false
67
+ @production = (@env == 'production') ? true : false
68
+ @test = (@env == 'test') ? true : false
69
+ end
70
+
71
+ def development?
72
+ @development
73
+ end
74
+
75
+ def production?
76
+ @production
77
+ end
78
+
79
+ def test?
80
+ @test
81
+ end
82
+
83
+ def start_app!
84
+ Isomorfeus.zeitwerk.setup
85
+ Isomorfeus::TopLevel.mount!
86
+ end
87
+
88
+ def force_render
89
+ begin
90
+ if Isomorfeus.top_component
91
+ Preact.find_dom_node(Isomorfeus.top_component) if on_browser? || on_desktop? # if not mounted will raise
92
+ if `typeof Opal.global.deepForceUpdate === 'undefined'`
93
+ Isomorfeus.top_component.JS.forceUpdate()
94
+ else
95
+ `Opal.global.deepForceUpdate(#{Isomorfeus.top_component})`
96
+ end
97
+ end
98
+ rescue Exception => e
99
+ # TODO try mount first
100
+ # if it fails
101
+ `console.error("force_render failed'! Error: " + #{e.message} + "! Reloading page.")`
102
+ `location.reload()` if on_browser?
103
+ end
104
+ nil
105
+ end
106
+ end
107
+
108
+ self.add_client_option(:client_init_class_names, [])
109
+ self.add_client_option(:client_init_after_store_class_names, [])
110
+ else
111
+ class << self
112
+ attr_reader :component_cache_init_block
113
+ attr_accessor :server_side_rendering
114
+ attr_accessor :ssr_hot_asset_url
115
+ attr_reader :env
116
+ attr_accessor :zeitwerk
117
+ attr_accessor :zeitwerk_lock
118
+
119
+ def component_cache_init(&block)
120
+ @component_cache_init_block = block
121
+ end
122
+
123
+ def configuration(&block)
124
+ block.call(self)
125
+ end
126
+
127
+ def env=(env_string)
128
+ @env = env_string ? env_string.to_s : 'development'
129
+ @development = (@env == 'development') ? true : false
130
+ @production = (@env == 'production') ? true : false
131
+ @test = (@env == 'test') ? true : false
132
+ end
133
+
134
+ def development?
135
+ @development
136
+ end
137
+
138
+ def production?
139
+ @production
140
+ end
141
+
142
+ def test?
143
+ @test
144
+ end
145
+
146
+ def ssr_contexts
147
+ @ssr_contexts ||= {}
148
+ end
149
+
150
+ def version
151
+ Isomorfeus::VERSION
152
+ end
153
+
154
+ def load_configuration(directory)
155
+ Dir.glob(File.join(directory, '*.rb')).sort.each do |file|
156
+ require File.expand_path(file)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ class << self
163
+ def raise_error(error_class: nil, message: nil, stack: nil)
164
+ error_class = RuntimeError unless error_class
165
+ execution_environment = if on_browser? then 'on Browser'
166
+ elsif on_ssr? then 'in Server Side Rendering'
167
+ elsif on_server? then 'on Server'
168
+ elsif on_mobile? then 'on Mobile'
169
+ elsif on_database? then 'on Database'
170
+ else
171
+ 'on Client'
172
+ end
173
+ error = error_class.new("Isomorfeus in #{env} #{execution_environment}:\n#{message}")
174
+ error.set_backtrace(stack) if stack
175
+
176
+ if Isomorfeus.development?
177
+ if RUBY_ENGINE == 'opal'
178
+ ecn = error_class ? error_class.name : ''
179
+ m = message ? message : ''
180
+ s = stack ? stack : ''
181
+ `console.error(ecn, m, s)`
182
+ else
183
+ STDERR.puts "#{ecn}: #{m}\n #{s}"
184
+ end
185
+ end
186
+ raise error
187
+ end
188
+ end
189
+ end