module HyperMesh
def self.load(&block)
ReactiveRecord.load(&block)
end
end
module ReactiveRecord
# will repeatedly execute the block until it is loaded
# immediately returns a promise that will resolve once the block is loaded
def self.load(&block)
promise = Promise.new
@load_stack ||= []
@load_stack << @loads_pending
@loads_pending = nil
result = block.call.itself
if @loads_pending
@blocks_to_load ||= []
@blocks_to_load << [Base.current_fetch_id, promise, block]
else
promise.resolve result
end
@loads_pending = @load_stack.pop
promise
rescue Exception => e
Hyperstack::Component::IsomorphicHelpers.log "ReactiveRecord.load exception raised during initial load: #{e}", :error
end
def self.loads_pending!
@loads_pending = true
end
def self.check_loads_pending
if @loads_pending
if Base.pending_fetches.count > 0
true
else # this happens when for example loading foo.x results in somebody looking at foo.y while foo.y is still being loaded
ReactiveRecord::WhileLoading.loaded_at Base.current_fetch_id
ReactiveRecord::WhileLoading.quiet!
false
end
end
end
def self.run_blocks_to_load(fetch_id, failure = nil)
if @blocks_to_load
blocks_to_load_now = @blocks_to_load.select { |data| data.first == fetch_id }
@blocks_to_load = @blocks_to_load.reject { |data| data.first == fetch_id }
@load_stack ||= []
blocks_to_load_now.each do |data|
id, promise, block = data
@load_stack << @loads_pending
@loads_pending = nil
result = block.call(failure)
if check_loads_pending && !failure
@blocks_to_load << [Base.current_fetch_id, promise, block]
else
promise.resolve result
end
@loads_pending = @load_stack.pop
end
end
rescue Exception => e
Hyperstack::Component::IsomorphicHelpers.log "ReactiveRecord.load exception raised during retry: #{e}", :error
end
# Adds while_loading feature to React
# to use attach a .while_loading handler to any element for example
# div { "displayed if everything is loaded" }.while_loading { "displayed while I'm loading" }
# the contents of the div will be switched (using javascript classes) depending on the state of contents of the first block
# To notify React that something is loading use React::WhileLoading.loading!
# once everything is loaded then do React::WhileLoading.loaded_at message (typically a time stamp just for debug purposes)
# class WhileLoading
#
# include Hyperstack::Component::IsomorphicHelpers
#
# before_first_mount do
# @css_to_preload = ""
# @while_loading_counter = 0
# end
#
# def self.get_next_while_loading_counter
# @while_loading_counter += 1
# end
#
# def self.preload_css(css)
# @css_to_preload += "#{css}\n"
# end
#
# def self.has_observers?
# Hyperstack::Internal::State::Variable.observed?(self, :loaded_at)
# end
#
# class << self
# alias :observed? :has_observers?
# end
#
# prerender_footer do
# "".tap { @css_to_preload = ""}
# end
#
# if RUBY_ENGINE == 'opal'
#
# # +: I DONT THINK WE USE opal-jquery in this module anymore - require 'opal-jquery' if opal_client?
# # -: You think wrong. add_style_sheet uses the jQuery $, after_mount too, others too
# # -: I removed those references. Now you think right.
#
# include Hyperstack::Component
#
# param :render_original_child
# param :loading
#
# class << self
#
# def loading?
# @is_loading
# end
#
# def loading!
# Hyperstack::Internal::Component::RenderingContext.waiting_on_resources = true
# Hyperstack::Internal::State::Variable.get(self, :loaded_at)
# # this was moved to where the fetch is actually pushed on to the fetch array in isomorphic base
# # Hyperstack::Internal::State::Variable.set(self, :quiet, false)
# @is_loading = true
# end
#
# def loaded_at(loaded_at)
# Hyperstack::Internal::State::Variable.set(self, :loaded_at, loaded_at)
# @is_loading = false
# end
#
# def quiet?
# Hyperstack::Internal::State::Variable.get(self, :quiet)
# end
#
# def page_loaded?
# Hyperstack::Internal::State::Variable.get(self, :page_loaded)
# end
#
# def quiet!
# Hyperstack::Internal::State::Variable.set(self, :quiet, true)
# after(1) { Hyperstack::Internal::State::Variable.set(self, :page_loaded, true) } unless on_opal_server? or @page_loaded
# @page_loaded = true
# end
#
# def add_style_sheet
# # directly assigning the code to the variable triggers a opal 0.10.5 compiler bug.
# unless @style_sheet_added
# %x{
# var style_el = document.createElement("style");
# style_el.setAttribute("type", "text/css");
# style_el.innerHTML = ".reactive_record_is_loading > .reactive_record_show_when_loaded { display: none !important; }\n" +
# ".reactive_record_is_loaded > .reactive_record_show_while_loading { display: none !important; }";
# document.head.append(style_el);
# }
# @style_sheet_added = true
# end
# end
#
# end
#
# def after_mount_and_update
# @waiting_on_resources = @Loading
# node = dom_node
# %x{
# Array.from(node.children).forEach(
# function(current_node, current_index, list_obj) {
# if (current_index > 0 && current_node.className.indexOf('reactive_record_show_when_loaded') === -1) {
# current_node.className = current_node.className + ' reactive_record_show_when_loaded';
# } else if (current_index == 0 && current_node.className.indexOf('reactive_record_show_while_loading') === -1) {
# current_node.className = current_node.className + ' reactive_record_show_while_loading';
# }
# }
# );
# }
# nil
# end
#
# before_mount do
# @uniq_id = WhileLoading.get_next_while_loading_counter
# WhileLoading.preload_css(
# ":not(.reactive_record_is_loading).reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1) {\n"+
# " display: none;\n"+
# "}\n"
# )
# end
#
# after_mount do
# WhileLoading.add_style_sheet
# after_mount_and_update
# end
#
# after_update :after_mount_and_update
#
# render do
# @RenderOriginalChild.call(@uniq_id)
# end
#
# end
#
# end
#
# end
#
# module Hyperstack
# module Component
#
# class Element
#
# def while_loading(display = "", &loading_display_block)
# original_block = block || -> () {}
#
# if display.respond_to? :as_node
# display = display.as_node
# block = lambda { display.render; instance_eval(&original_block) }
# elsif !loading_display_block
# block = lambda { display; instance_eval(&original_block) }
# else
# block = ->() { instance_eval(&loading_display_block); instance_eval(&original_block) }
# end
# loading_child = Internal::Component::RenderingContext.build do |buffer|
# Hyperstack::Internal::Component::RenderingContext.render(:span, key: 1, &loading_display_block) #{ to_s }
# buffer.dup
# end
# children = `#{@native}.props.children.slice(0)`
# children.unshift(loading_child[0].instance_eval { @native })
# @native = `React.cloneElement(#{@native}, #{@properties.shallow_to_n}, #{children})`
# render_original_child = lambda do |uniq_id|
# classes = [
# @properties[:class], @properties[:className],
# "reactive_record_while_loading_container_#{uniq_id}"
# ].compact.join(' ')
# @properties.merge!({
# "data-reactive_record_while_loading_container_id" => uniq_id,
# "data-reactive_record_enclosing_while_loading_container_id" => uniq_id,
# className: classes
# })
# @native = `React.cloneElement(#{@native}, #{@properties.shallow_to_n})`
# render
# end
# delete
# ReactAPI.create_element(
# ReactiveRecord::WhileLoading,
# loading: waiting_on_resources,
# render_original_child: render_original_child)
# end
#
# def hide_while_loading
# while_loading
# end
#
# end
# end
# end
#
class WhileLoading
include Hyperstack::Component::IsomorphicHelpers
before_first_mount do
@css_to_preload = ""
@while_loading_counter = 0
end
def self.get_next_while_loading_counter
@while_loading_counter += 1
end
def self.preload_css(css)
@css_to_preload += "#{css}\n"
end
def self.has_observers?
Hyperstack::Internal::State::Variable.observed?(self, :loaded_at)
end
class << self
alias :observed? :has_observers?
end
prerender_footer do
"".tap { @css_to_preload = ""}
end
if RUBY_ENGINE == 'opal'
# +: I DONT THINK WE USE opal-jquery in this module anymore - require 'opal-jquery' if opal_client?
# -: You think wrong. add_style_sheet uses the jQuery $, after_mount too, others too
# -: I removed those references. Now you think right.
include Hyperstack::Component
param :loading
param :loaded_children
param :loading_children
param :element_type
param :element_props
others :other_props
param :display, default: ''
class << self
def loading?
@is_loading
end
def loading!
Hyperstack::Internal::Component::RenderingContext.waiting_on_resources = true
Hyperstack::Internal::State::Variable.get(self, :loaded_at)
# this was moved to where the fetch is actually pushed on to the fetch array in isomorphic base
# Hyperstack::Internal::State::Variable.set(self, :quiet, false)
@is_loading = true
end
def loaded_at(loaded_at)
Hyperstack::Internal::State::Variable.set(self, :loaded_at, loaded_at)
@is_loading = false
end
def quiet?
Hyperstack::Internal::State::Variable.get(self, :quiet)
end
def page_loaded?
Hyperstack::Internal::State::Variable.get(self, :page_loaded)
end
def quiet!
Hyperstack::Internal::State::Variable.set(self, :quiet, true)
after(1) { Hyperstack::Internal::State::Variable.set(self, :page_loaded, true) } unless on_opal_server? or @page_loaded
@page_loaded = true
end
def add_style_sheet
# directly assigning the code to the variable triggers a opal 0.10.5 compiler bug.
unless @style_sheet_added
%x{
var style_el = document.createElement("style");
style_el.setAttribute("type", "text/css");
style_el.innerHTML = ".reactive_record_is_loading > .reactive_record_show_when_loaded { display: none !important; }\n" +
".reactive_record_is_loaded > .reactive_record_show_while_loading { display: none !important; }";
document.head.append(style_el);
}
@style_sheet_added = true
end
end
end
def after_mount_and_update
@waiting_on_resources = @Loading
node = dom_node
%x{
Array.from(node.children).forEach(
function(current_node, current_index, list_obj) {
if (current_index > 0 && current_node.className.indexOf('reactive_record_show_when_loaded') === -1) {
current_node.className = current_node.className + ' reactive_record_show_when_loaded';
} else if (current_index == 0 && current_node.className.indexOf('reactive_record_show_while_loading') === -1) {
current_node.className = current_node.className + ' reactive_record_show_while_loading';
}
}
);
}
nil
end
before_mount do
@uniq_id = WhileLoading.get_next_while_loading_counter
WhileLoading.preload_css(
":not(.reactive_record_is_loading).reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1) {\n"+
" display: none;\n"+
"}\n"
)
end
after_mount do
WhileLoading.add_style_sheet
after_mount_and_update
end
after_update :after_mount_and_update
render do
# return ReactAPI.create_element(@ElementType[0], @ElementProps.dup) do
# @LoadedChildren
# end
props = @ElementProps.dup
classes = [
props[:class], props[:className],
@OtherProps.delete(:class), @OtherProps.delete(:className),
"reactive_record_while_loading_container_#{@uniq_id}"
].compact.join(" ")
props.merge!({
"data-reactive_record_while_loading_container_id" => @uniq_id,
"data-reactive_record_enclosing_while_loading_container_id" => @uniq_id,
class: classes
})
props.merge!(@OtherProps)
ReactAPI.create_element(@ElementType[0], props) do
@LoadingChildren + @LoadedChildren
end.tap { |e| e.waiting_on_resources = @Loading }
end
end
end
end
module Hyperstack
module Component
class Element
def while_loading(display = "", &loading_display_block)
loaded_children = []
loaded_children = block.call.dup if block
if loaded_children.last.is_a? String
loaded_children <<
Hyperstack::Internal::Component::ReactWrapper.create_element(:span) { loaded_children.pop }
end
if display.respond_to? :as_node
display = display.as_node
loading_display_block = lambda { display.render }
elsif !loading_display_block
loading_display_block = lambda { display }
end
loading_children = Internal::Component::RenderingContext.build do |buffer|
Hyperstack::Internal::Component::RenderingContext.render(:span, &loading_display_block) #{ to_s }
buffer.dup
end
as_node
new_element = ReactAPI.create_element(
ReactiveRecord::WhileLoading,
loading: waiting_on_resources,
loading_children: loading_children,
loaded_children: loaded_children,
element_type: [type],
element_props: properties)
#Internal::Component::RenderingContext.replace(self, new_element)
end
def hide_while_loading
while_loading
end
end
end
end
if RUBY_ENGINE == 'opal'
module Hyperstack
module Component
def quiet?
Hyperstack::Internal::State::Variable.get(ReactiveRecord::WhileLoading, :quiet)
end
alias_method :original_component_did_mount, :component_did_mount
def component_did_mount(*args)
original_component_did_mount(*args)
reactive_record_link_to_enclosing_while_loading_container
reactive_record_link_set_while_loading_container_class
end
alias_method :original_component_did_update, :component_did_update
def component_did_update(*args)
original_component_did_update(*args)
reactive_record_link_set_while_loading_container_class
end
def reactive_record_link_to_enclosing_while_loading_container
# Call after any component mounts - attaches the containers loading id to this component
# Fyi, the while_loading container is responsible for setting its own link to itself
node = dom_node
%x{
if (typeof node === "undefined" || node === null) return;
var node_wl_attr = node.getAttribute('data-reactive_record_enclosing_while_loading_container_id');
if (node_wl_attr === null || node_wl_attr === "") {
var while_loading_container = node.closest('[data-reactive_record_while_loading_container_id]');
if (while_loading_container !== null) {
var container_id = while_loading_container.getAttribute('data-reactive_record_while_loading_container_id');
node.setAttribute('data-reactive_record_enclosing_while_loading_container_id', container_id);
}
}
}
end
def reactive_record_link_set_while_loading_container_class
node = dom_node
loading = (waiting_on_resources ? `true` : `false`)
%x{
if (typeof node === "undefined" || node === null) return;
var while_loading_container_id = node.getAttribute('data-reactive_record_while_loading_container_id');
if (#{!self.is_a?(ReactiveRecord::WhileLoading)} && while_loading_container_id !== null && while_loading_container_id !== "") {
return;
}
var enc_while_loading_container_id = node.getAttribute('data-reactive_record_enclosing_while_loading_container_id');
if (enc_while_loading_container_id !== null && enc_while_loading_container_id !== "") {
var while_loading_container = document.body.querySelector('[data-reactive_record_while_loading_container_id="'+enc_while_loading_container_id+'"]');
if (loading) {
node.className = node.className.replace(/reactive_record_is_loaded/g, '').replace(/ /g, ' ');
if (node.className.indexOf('reactive_record_is_loading') === -1) {
node.className = node.className + ' reactive_record_is_loading';
}
if (while_loading_container !== null) {
while_loading_container.className = while_loading_container.className.replace(/reactive_record_is_loaded/g, '').replace(/ /g, ' ');
if (while_loading_container.className.indexOf('reactive_record_is_loading') === -1) {
while_loading_container.className = while_loading_container.className + ' reactive_record_is_loading';
}
}
} else if (node.className.indexOf('reactive_record_is_loaded') === -1) {
if (while_loading_container_id === null || while_loading_container_id === "") {
node.className = node.className.replace(/reactive_record_is_loading/g, '').replace(/ /g, ' ');
if (node.className.indexOf('reactive_record_is_loaded') === -1) {
node.className = node.className + ' reactive_record_is_loaded';
}
}
if (while_loading_container.className.indexOf('reactive_record_is_loaded') === -1) {
var loading_children = while_loading_container.querySelectorAll('[data-reactive_record_enclosing_while_loading_container_id="'+enc_while_loading_container_id+'"].reactive_record_is_loading');
if (loading_children.length === 0) {
while_loading_container.className = while_loading_container.className.replace(/reactive_record_is_loading/g, '').replace(/ /g, ' ');
if (while_loading_container.className.indexOf('reactive_record_is_loaded') === -1) {
while_loading_container.className = while_loading_container.className + ' reactive_record_is_loaded';
}
}
}
}
}
}
end
end
end
end