isomorfeus-preact 10.5.0

Sign up to get free protection for your applications and to get access to all the features.
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