module Redux class Store include Native::Wrapper class << self def add_middleware(middleware) if Isomorfeus.store `console.warning("Adding middleware after Store initialization may have side effects! Saving state and initializing new store with restored state!")` middlewares << middleware preloaded_state = Isomorfeus.store.get_state init! else middlewares << middleware end end def add_reducer(reducer) if Isomorfeus.store # if the store has been initalized already, add the reducer to the instance Isomorfeus.store.add_reducer(reducer) else # otherwise just add it to the reducers, so that they will be used when initializing the store preloaded_state[reducer.keys.first] = {} unless preloaded_state.key?(reducer.keys.first) reducers.merge!(reducer) end end def add_reducers(new_reducers) if Isomorfeus.store # if the store has been initalized already, add the reducer to the instance Isomorfeus.store.add_reducers(new_reducers) else # otherwise just add it to the reducers, so that they will be used when initializing the store new_reducers.each do |key, value| add_reducer(key => value) end end end # called from Isomorfeus.init def init! next_reducer = Redux.combine_reducers(@reducers) if middlewares.any? enhancer = Redux.apply_middleware(middlewares) Redux::Store.new(next_reducer, preloaded_state, enhancer) else Redux::Store.new(next_reducer, preloaded_state) end end def middlewares @middlewares ||= [] end def preloaded_state_merge!(ruby_hash) preloaded_state.merge!(ruby_hash) end def preloaded_state @preloaded_state ||= {} end def preloaded_state=(ruby_hash) @preloaded_state = ruby_hash end def reducers @reducers ||= {} end end def initialize(reducer, preloaded_state = nil, enhancer = nil) @deferred_actions = {} @deferred_dispatcher = nil @last_dispatch_time = Time.now %x{ var ogre = Opal.global.Redux; var compose = (typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || ogre.compose; if (enhancer == nil) { enhancer = null; } if (typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION__) { var devext_enhance = window.__REDUX_DEVTOOLS_EXTENSION__(); if (enhancer) { enhancer = compose([enhancer, devext_enhance]); } else { enhancer = devext_enhance; } } if (preloaded_state == nil) { preloaded_state = null; } else if (typeof preloaded_state.$class === "function" && preloaded_state.$class() == "Hash") { if (preloaded_state.$size() == 0) { preloaded_state = null; } else { preloaded_state = preloaded_state.$to_n(); } } if (enhancer && preloaded_state) { this.native = ogre.createStore(reducer, preloaded_state, enhancer); } else if (preloaded_state) { this.native = ogre.createStore(reducer, preloaded_state); } else if (enhancer) { this.native = ogre.createStore(reducer, enhancer); } else { this.native = ogre.createStore(reducer); } } end def add_reducer(reducer) self.class.reducers.merge!(reducer) next_reducer = Redux.combine_reducers(self.class.reducers) replace_reducer(next_reducer) end def add_reducers(new_reducers) self.class.reducers.merge!(new_reducers) next_reducer = Redux.combine_reducers(self.class.reducers) replace_reducer(next_reducer) end def dispatch(action) %x{ if (typeof action.$class === "function" && action.$class() == "Hash") { action = action.$to_n(); } this.native.dispatch(action); } end def get_state %x{ let res = this.native.getState(); if (typeof(res) === 'object' && !Array.isArray(res) && res !== null) { return Opal.Hash.$new(res); } return res; } end def collect_and_defer_dispatch(action) if !Isomorfeus.on_ssr? type = action.delete(:type) @deferred_actions[type] = [] unless @deferred_actions.key?(type) @deferred_actions[type].push(action) @last_dispatch_time = `Date.now()` # `console.log(#@last_dispatch_time)` deferred_dispatcher(`Date.now()`) unless @deferred_dispatcher else dispatch(action) end nil end def replace_reducer(next_reducer) `this.native.replaceReducer(next_reducer)` end # returns function needed to unsubscribe the listener def subscribe(&listener) `this.native.subscribe(function() { return listener.$call(); })` end private def deferred_dispatcher(first) @deferred_dispatcher = true %x{ setTimeout(function() { if (#{wait_longer?(first)}) { #{deferred_dispatcher(first)} } else { #{dispatch_deferred_dispatches} } }, 10) } end def dispatch_deferred_dispatches # `console.log(Date.now())` @deferred_dispatcher = false actions = @deferred_actions @deferred_actions = {} actions.each do |type, data| dispatch(type: type, collected: data) end end def wait_longer?(first) t = `Date.now()` time_since_first = `t - first` # `console.log('delta', time_since_first)` return true if `typeof Opal.Preact !== 'undefined' && typeof Opal.Preact.render_buffer !== 'undefined' && Opal.Preact.render_buffer.length > 0 && time_since_first < 1000` return false if time_since_first > 100 # ms return false if (`t - #@last_dispatch_time`) > 9 # ms return true end end end