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,101 @@
1
+ module Isomorfeus
2
+ class TopLevel
3
+ class << self
4
+ def mount!
5
+ Isomorfeus.init
6
+ Isomorfeus::TopLevel.on_ready do
7
+ root_element = `document.querySelector('div[data-iso-root]')`
8
+ Isomorfeus.raise_error(message: "Isomorfeus root element not found!") unless root_element
9
+ component_name = root_element.JS.getAttribute('data-iso-root')
10
+ Isomorfeus.env = root_element.JS.getAttribute('data-iso-env')
11
+ user_sid = root_element.JS.getAttribute('data-iso-usid')
12
+ Isomorfeus.current_user_sid =`JSON.parse(user_sid)` if user_sid
13
+ component = nil
14
+ begin
15
+ component = component_name.constantize
16
+ rescue Exception => e
17
+ `console.warn("Deferring mount: " + #{e.message})`
18
+ @timeout_start = Time.now unless @timeout_start
19
+ if (Time.now - @timeout_start) < 10
20
+ `setTimeout(Opal.Isomorfeus.TopLevel['$mount!'], 100)`
21
+ else
22
+ `console.error("Unable to mount '" + #{component_name} + "'!")`
23
+ end
24
+ end
25
+ if component
26
+ props_json = root_element.JS.getAttribute('data-iso-props')
27
+ props = `Opal.Hash.$new(JSON.parse(props_json))`
28
+ raw_hydrated = root_element.JS.getAttribute('data-iso-hydrated')
29
+ hydrated = (raw_hydrated && raw_hydrated == "true")
30
+ %x{
31
+ if (global.ServerSideRenderingStateJSON) {
32
+ var state = global.ServerSideRenderingStateJSON;
33
+ var keys = Object.keys(state);
34
+ for(var i=0; i < keys.length; i++) {
35
+ if (Object.keys(state[keys[i]]).length > 0) {
36
+ global.Opal.Isomorfeus.store.native.dispatch({ type: keys[i].toUpperCase(), set_state: state[keys[i]] });
37
+ }
38
+ }
39
+ }
40
+ }
41
+ Isomorfeus.execute_init_after_store_classes
42
+ begin
43
+ result = Isomorfeus::TopLevel.mount_component(component, props, root_element, hydrated)
44
+ @tried_another_time = false
45
+ result
46
+ rescue Exception => e
47
+ if !@tried_another_time
48
+ @tried_another_time = true
49
+ `console.warn("Deferring mount: " + #{e.message})`
50
+ `console.error(#{e.backtrace.join("\n")})`
51
+ `setTimeout(Opal.Isomorfeus.TopLevel['$mount!'], 250)`
52
+ else
53
+ `console.error("Unable to mount '" + #{component_name} + "'! Error: " + #{e.message} + "!")`
54
+ `console.error(#{e.backtrace.join("\n")})`
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def on_ready(&block)
62
+ %x{
63
+ function run() { block.$call() };
64
+ function ready_fun(fn) {
65
+ if (document.readyState === "complete" || document.readyState === "interactive") {
66
+ setTimeout(fn, 1);
67
+ } else {
68
+ document.addEventListener("DOMContentLoaded", fn);
69
+ }
70
+ }
71
+ ready_fun(run);
72
+ }
73
+ end
74
+
75
+ def on_ready_mount(component, props = nil, element_query = nil)
76
+ # init in case it hasn't been run yet
77
+ Isomorfeus.init
78
+ on_ready do
79
+ Isomorfeus::TopLevel.mount_component(component, props, element_query)
80
+ end
81
+ end
82
+
83
+ def mount_component(component, props, element_or_query, hydrated = false)
84
+ if `(typeof element_or_query === 'string')` || (`(typeof element_or_query.$class === 'function')` && element_or_query.class == String)
85
+ element = `document.body.querySelector(element_or_query)`
86
+ elsif `(typeof element_or_query.$is_a === 'function')` && element_or_query.is_a?(Browser::Element)
87
+ element = element_or_query.to_n
88
+ else
89
+ element = element_or_query
90
+ end
91
+
92
+ top = if hydrated
93
+ Preact.hydrate(Preact.create_element(component, props), element)
94
+ else
95
+ Preact.render(Preact.create_element(component, props), element)
96
+ end
97
+ Isomorfeus.top_component = top if top
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,27 @@
1
+ module Isomorfeus
2
+ class TopLevel
3
+ class << self
4
+ attr_accessor :ssr_route_path
5
+ attr_accessor :transport_ws_url
6
+
7
+ def mount!
8
+ # nothing, but keep it for compatibility with browser
9
+ end
10
+
11
+ def render_component_to_string(component_name, props)
12
+ component = nil
13
+ %x{
14
+ if (typeof component_name === 'string' || component_name instanceof String) {
15
+ component = component_name.split(".").reduce(function(o, x) {
16
+ return (o !== null && typeof o[x] !== "undefined" && o[x] !== null) ? o[x] : null;
17
+ }, Opal.global)
18
+ } else {
19
+ component = component_name;
20
+ }
21
+ }
22
+ component = Isomorfeus.cached_component_class(component_name) unless component
23
+ Preact.render_to_string(Preact.create_element(component, `Opal.Hash.$new(props)`))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module LucidApp
2
+ module Api
3
+ def self.included(base)
4
+ base.instance_exec do
5
+ def theme(theme_hash = nil, &block)
6
+ theme_hash = block.call if block_given?
7
+ if theme_hash
8
+ %x{
9
+ let css;
10
+ if (typeof theme_hash.$to_n === 'function') { css = theme_hash.$to_n(); }
11
+ else { css = theme_hash; }
12
+ let nano_styles = Opal.global.NanoCSSInstance.sheet(css, "LucidAppTheme");
13
+ base.css_theme = #{::LucidComponent::StylesWrapper.new(`nano_styles`)};
14
+ }
15
+ end
16
+ `base.css_theme`
17
+ end
18
+ alias_method :theme=, :theme
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module LucidApp
2
+ class Base
3
+ def self.inherited(base)
4
+ base.include(::LucidApp::Mixin)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module LucidApp
2
+ module Mixin
3
+ def self.included(base)
4
+ base.include(::Native::Wrapper)
5
+ base.extend(::LucidApp::NativeComponentConstructor)
6
+ base.include(::Preact::Component::Elements)
7
+ base.extend(::LucidPropDeclaration::Mixin)
8
+ base.include(::Preact::Component::Api)
9
+ base.include(::Preact::Component::Callbacks)
10
+ base.include(::LucidComponent::Api)
11
+ base.include(::LucidComponent::StylesApi)
12
+ base.include(::LucidApp::Api)
13
+ base.include(::LucidComponent::Initializer)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,91 @@
1
+ module LucidApp
2
+ module NativeComponentConstructor
3
+ # for should_component_update we apply ruby semantics for comparing props
4
+ # to do so, we convert the props to ruby hashes and then compare
5
+ # this makes sure, that for example rubys Nil object gets handled properly
6
+ def self.extended(base)
7
+ component_name = base.to_s
8
+ %x{
9
+ base.css_styles = null;
10
+ base.css_theme = null;
11
+ base.preload_block = null;
12
+ base.while_loading_block = null;
13
+
14
+ base.preact_component = class extends Opal.global.Preact.Component {
15
+ constructor(props) {
16
+ super(props);
17
+ const oper = Opal.Preact;
18
+ if (base.$default_state_defined()) {
19
+ this.state = base.$state().$to_n();
20
+ } else {
21
+ this.state = {};
22
+ };
23
+ this.state.isomorfeus_store_state = Opal.Isomorfeus.store.native.getState();
24
+ var current_store_state = this.state.isomorfeus_store_state;
25
+ if (typeof current_store_state.class_state[#{component_name}] !== "undefined") {
26
+ this.state.class_state = {};
27
+ this.state.class_state[#{component_name}] = current_store_state.class_state[#{component_name}];
28
+ } else {
29
+ this.state.class_state = {};
30
+ this.state.class_state[#{component_name}] = {};
31
+ };
32
+ this.__ruby_instance = base.$new(this);
33
+ var defined_refs = #{base.defined_refs};
34
+ for (var ref in defined_refs) {
35
+ if (defined_refs[ref] != null) {
36
+ this[ref] = function(element) {
37
+ element = oper.native_element_or_component_to_ruby(element);
38
+ #{`this.__ruby_instance`.instance_exec(`element`, &`defined_refs[ref]`)}
39
+ }
40
+ this[ref] = this[ref].bind(this);
41
+ } else {
42
+ this[ref] = Opal.global.Preact.createRef();
43
+ }
44
+ }
45
+ if (base.preload_block) {
46
+ oper.active_redux_components.push(this);
47
+ this.state.preloaded = this.__ruby_instance.$execute_preload_block();
48
+ oper.active_redux_components.pop();
49
+ }
50
+ this.listener = this.listener.bind(this);
51
+ this.unsubscriber = Opal.Isomorfeus.store.native.subscribe(this.listener);
52
+ }
53
+ static get displayName() {
54
+ return #{component_name};
55
+ }
56
+ render(props, state) {
57
+ const oper = Opal.Preact;
58
+ oper.render_buffer.push([]);
59
+ // console.log("lucid app pushed", oper.render_buffer, oper.render_buffer.toString());
60
+ oper.active_components.push(this);
61
+ oper.active_redux_components.push(this);
62
+ let block_result;
63
+ if (base.while_loading_block && !state.preloaded) { block_result = #{`this.__ruby_instance`.instance_exec(&`base.while_loading_block`)}; }
64
+ else { block_result = #{`this.__ruby_instance`.instance_exec(&`base.render_block`)}; }
65
+ if (block_result && block_result !== nil) { oper.render_block_result(block_result); }
66
+ oper.active_redux_components.pop();
67
+ oper.active_components.pop();
68
+ let children = oper.render_buffer.pop();
69
+ // console.log("lucid app popping", oper.render_buffer, oper.render_buffer.toString());
70
+ return Opal.global.Preact.createElement(Opal.global.LucidApplicationContext.Provider, { value: { iso_store: this.state.isomorfeus_store_state, iso_theme: base.css_theme }}, children);
71
+ }
72
+ data_access() {
73
+ this.state.isomorfeus_store_state;
74
+ }
75
+ listener() {
76
+ let next_state = Opal.Isomorfeus.store.native.getState();
77
+ this.setState({ isomorfeus_store_state: next_state });
78
+ }
79
+ componentWillUnmount() {
80
+ if (typeof this.unsubscriber === "function") { this.unsubscriber(); }
81
+ }
82
+ validateProp(props, propName, componentName) {
83
+ try { base.$validate_prop(propName, props[propName]) }
84
+ catch (e) { return new Error(componentName + ": Error: prop validation failed: " + e.message); }
85
+ return null;
86
+ }
87
+ }
88
+ }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ module LucidComponent
2
+ module Api
3
+ def self.included(base)
4
+ base.instance_exec do
5
+ # store
6
+ attr_accessor :app_store
7
+ attr_accessor :class_store
8
+ attr_accessor :store
9
+
10
+ def class_store
11
+ @class_store ||= ::LucidComponent::ClassStoreProxy.new(self.to_s)
12
+ end
13
+
14
+ # preloading
15
+ def preload(&block)
16
+ `base.preload_block = block`
17
+ component_did_mount do
18
+ unless self.state.preloaded
19
+ @_preload_promise.then { self.state.preloaded = true }.fail do |result|
20
+ err_text = "#{self.class.name}: preloading failed, last result: #{result.nil? ? 'nil' : result}!"
21
+ `console.error(err_text)`
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def while_loading(option = nil, &block)
28
+ wl_block = proc do
29
+ if @_preload_promise.resolved?
30
+ instance_exec(&`base.render_block`)
31
+ else
32
+ instance_exec(&block)
33
+ end
34
+ end
35
+ `base.while_loading_block = wl_block`
36
+ end
37
+ end
38
+
39
+ # stores
40
+ def local_store
41
+ LocalStore
42
+ end
43
+
44
+ def session_store
45
+ SessionStore
46
+ end
47
+
48
+ def theme
49
+ props.theme
50
+ end
51
+
52
+ # preloading
53
+ def execute_preload_block
54
+ @_preload_promise = instance_exec(&self.class.JS[:preload_block])
55
+ @_preload_promise.resolved?
56
+ end
57
+
58
+ def preloaded?
59
+ !!state.preloaded
60
+ end
61
+
62
+ # requires transport
63
+ def current_user
64
+ Isomorfeus.current_user
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,37 @@
1
+ module LucidComponent
2
+ class AppStoreProxy
3
+ def initialize(component_instance)
4
+ if component_instance
5
+ @native = component_instance.to_n
6
+ @component_instance = component_instance
7
+ end
8
+ end
9
+
10
+ def [](key)
11
+ method_missing(key)
12
+ end
13
+
14
+ def []=(key, value)
15
+ method_missing(key, value)
16
+ end
17
+
18
+ def method_missing(key, *args, &block)
19
+ if `args.length > 0`
20
+ # set class state, simply a dispatch
21
+ action = { type: 'APPLICATION_STATE', name: (`key.endsWith('=')` ? key.chop : key), value: args[0] }
22
+ Isomorfeus.store.collect_and_defer_dispatch(action)
23
+ else
24
+ # check if we have a component local state value
25
+ if @native && `#@native.props.iso_store`
26
+ if `#@native.props.iso_store.application_state && #@native.props.iso_store.application_state.hasOwnProperty(key)`
27
+ return @native.JS[:props].JS[:iso_store].JS[:application_state].JS[key]
28
+ end
29
+ else
30
+ return AppStore[key]
31
+ end
32
+ # otherwise return nil
33
+ return nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module LucidComponent
2
+ class Base
3
+ def self.inherited(base)
4
+ base.include(::LucidComponent::Mixin)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module LucidComponent
2
+ class ClassStoreProxy
3
+ def initialize(component_name, component_instance = nil, native = nil)
4
+ @component_name = component_name
5
+ if component_instance
6
+ @native = native
7
+ @component_instance = component_instance
8
+ end
9
+ end
10
+
11
+ def [](key)
12
+ method_missing(key)
13
+ end
14
+
15
+ def []=(key, value)
16
+ method_missing(key, value)
17
+ end
18
+
19
+ def method_missing(key, *args, &block)
20
+ if `args.length > 0`
21
+ # set class state, simply a dispatch
22
+ action = { type: 'CLASS_STATE', class: @component_name, name: (`key.endsWith('=')` ? key.chop : key), value: args[0] }
23
+ Isomorfeus.store.collect_and_defer_dispatch(action)
24
+ else
25
+ # get class state
26
+ # check if we have a component local state value
27
+ if @native && @native.JS[:props].JS[:iso_store]
28
+ if @native.JS[:props].JS[:iso_store].JS[:class_state] &&
29
+ @native.JS[:props].JS[:iso_store].JS[:class_state].JS[@component_name] &&
30
+ @native.JS[:props].JS[:iso_store].JS[:class_state].JS[@component_name].JS.hasOwnProperty(key)
31
+ return @native.JS[:props].JS[:iso_store].JS[:class_state].JS[@component_name].JS[key]
32
+ end
33
+ else
34
+ a_state = Isomorfeus.store.get_state
35
+ if a_state.key?(:class_state) && a_state[:class_state].key?(@component_name) && a_state[:class_state][@component_name].key?(key)
36
+ return a_state[:class_state][@component_name][key]
37
+ end
38
+ end
39
+ # otherwise return nil
40
+ return nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ module LucidComponent
2
+ module Initializer
3
+ def initialize(native_component)
4
+ @native = native_component
5
+ @app_store = LucidComponent::AppStoreProxy.new(self)
6
+ @class_store = LucidComponent::ClassStoreProxy.new(self.class.to_s, self, @native)
7
+ # @iso_store = Isomorfeus::IsomorphicStoreProxy.new(self)
8
+ # @local_store = Isomorfeus::LocalStoreProxy.new(self)
9
+ @store = LucidComponent::InstanceStoreProxy.new(self)
10
+ @props = `Opal.Preact.Component.Props.$new(#@native)`
11
+ @state = `Opal.Preact.Component.State.$new(#@native)`
12
+ end
13
+ end
14
+ end