/home/bdqbpbxa/api-uniferx.goodface.com.ua/vendor/swagger-api/swagger-ui/src/core/system.js
import React from "react"
import { createStore, applyMiddleware, bindActionCreators, compose } from "redux"
import Im, { fromJS, Map } from "immutable"
import deepExtend from "deep-extend"
import { combineReducers } from "redux-immutable"
import { serializeError } from "serialize-error"
import merge from "lodash/merge"
import { NEW_THROWN_ERR } from "core/plugins/err/actions"
import win from "core/window"
import { systemThunkMiddleware, isFn, objMap, objReduce, isObject, isArray, isFunc } from "core/utils"
const idFn = a => a
// Apply middleware that gets sandwitched between `dispatch` and the reducer function(s)
function createStoreWithMiddleware(rootReducer, initialState, getSystem) {
let middlwares = [
// createLogger( {
// stateTransformer: state => state && state.toJS()
// } ),
systemThunkMiddleware( getSystem )
]
const composeEnhancers = win.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
return createStore(rootReducer, initialState, composeEnhancers(
applyMiddleware( ...middlwares )
))
}
export default class Store {
constructor(opts={}) {
deepExtend(this, {
state: {},
plugins: [],
pluginsOptions: {},
system: {
configs: {},
fn: {},
components: {},
rootInjects: {},
statePlugins: {}
},
boundSystem: {},
toolbox: {}
}, opts)
this.getSystem = this._getSystem.bind(this)
// Bare system (nothing in it, besides the state)
this.store = configureStore(idFn, fromJS(this.state), this.getSystem )
// will be the system + Im, we can add more tools when we need to
this.buildSystem(false)
// Bootstrap plugins
this.register(this.plugins)
}
getStore() {
return this.store
}
register(plugins, rebuild=true) {
var pluginSystem = combinePlugins(plugins, this.getSystem(), this.pluginsOptions)
systemExtend(this.system, pluginSystem)
if(rebuild) {
this.buildSystem()
}
const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem())
if(needAnotherRebuild) {
this.buildSystem()
}
}
buildSystem(buildReducer=true) {
let dispatch = this.getStore().dispatch
let getState = this.getStore().getState
this.boundSystem = Object.assign({},
this.getRootInjects(),
this.getWrappedAndBoundActions(dispatch),
this.getWrappedAndBoundSelectors(getState, this.getSystem),
this.getStateThunks(getState),
this.getFn(),
this.getConfigs()
)
if(buildReducer)
this.rebuildReducer()
}
_getSystem() {
return this.boundSystem
}
getRootInjects() {
return Object.assign({
getSystem: this.getSystem,
getStore: this.getStore.bind(this),
getComponents: this.getComponents.bind(this),
getState: this.getStore().getState,
getConfigs: this._getConfigs.bind(this),
Im,
React
}, this.system.rootInjects || {})
}
_getConfigs(){
return this.system.configs
}
getConfigs() {
return {
configs: this.system.configs
}
}
setConfigs(configs) {
this.system.configs = configs
}
rebuildReducer() {
this.store.replaceReducer(buildReducer(this.system.statePlugins))
}
/**
* Generic getter from system.statePlugins
*
*/
getType(name) {
let upName = name[0].toUpperCase() + name.slice(1)
return objReduce(this.system.statePlugins, (val, namespace) => {
let thing = val[name]
if(thing)
return {[namespace+upName]: thing}
})
}
getSelectors() {
return this.getType("selectors")
}
getActions() {
let actionHolders = this.getType("actions")
return objMap(actionHolders, (actions) => {
return objReduce(actions, (action, actionName) => {
if(isFn(action))
return {[actionName]: action}
})
})
}
getWrappedAndBoundActions(dispatch) {
let actionGroups = this.getBoundActions(dispatch)
return objMap(actionGroups, (actions, actionGroupName) => {
let wrappers = this.system.statePlugins[actionGroupName.slice(0,-7)].wrapActions
if(wrappers) {
return objMap(actions, (action, actionName) => {
let wrap = wrappers[actionName]
if(!wrap) {
return action
}
if(!Array.isArray(wrap)) {
wrap = [wrap]
}
return wrap.reduce((acc, fn) => {
let newAction = (...args) => {
return fn(acc, this.getSystem())(...args)
}
if(!isFn(newAction)) {
throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)")
}
return wrapWithTryCatch(newAction)
}, action || Function.prototype)
})
}
return actions
})
}
getWrappedAndBoundSelectors(getState, getSystem) {
let selectorGroups = this.getBoundSelectors(getState, getSystem)
return objMap(selectorGroups, (selectors, selectorGroupName) => {
let stateName = [selectorGroupName.slice(0, -9)] // selectors = 9 chars
let wrappers = this.system.statePlugins[stateName].wrapSelectors
if(wrappers) {
return objMap(selectors, (selector, selectorName) => {
let wrap = wrappers[selectorName]
if(!wrap) {
return selector
}
if(!Array.isArray(wrap)) {
wrap = [wrap]
}
return wrap.reduce((acc, fn) => {
let wrappedSelector = (...args) => {
return fn(acc, this.getSystem())(getState().getIn(stateName), ...args)
}
if(!isFn(wrappedSelector)) {
throw new TypeError("wrapSelector needs to return a function that returns a new function (ie the wrapped action)")
}
return wrappedSelector
}, selector || Function.prototype)
})
}
return selectors
})
}
getStates(state) {
return Object.keys(this.system.statePlugins).reduce((obj, key) => {
obj[key] = state.get(key)
return obj
}, {})
}
getStateThunks(getState) {
return Object.keys(this.system.statePlugins).reduce((obj, key) => {
obj[key] = ()=> getState().get(key)
return obj
}, {})
}
getFn() {
return {
fn: this.system.fn
}
}
getComponents(component) {
const res = this.system.components[component]
if(Array.isArray(res)) {
return res.reduce((ori, wrapper) => {
return wrapper(ori, this.getSystem())
})
}
if(typeof component !== "undefined") {
return this.system.components[component]
}
return this.system.components
}
getBoundSelectors(getState, getSystem) {
return objMap(this.getSelectors(), (obj, key) => {
let stateName = [key.slice(0, -9)] // selectors = 9 chars
const getNestedState = ()=> getState().getIn(stateName)
return objMap(obj, (fn) => {
return (...args) => {
let res = wrapWithTryCatch(fn).apply(null, [getNestedState(), ...args])
// If a selector returns a function, give it the system - for advanced usage
if(typeof(res) === "function")
res = wrapWithTryCatch(res)(getSystem())
return res
}
})
})
}
getBoundActions(dispatch) {
dispatch = dispatch || this.getStore().dispatch
const actions = this.getActions()
const process = creator =>{
if( typeof( creator ) !== "function" ) {
return objMap(creator, prop => process(prop))
}
return ( ...args )=>{
var action = null
try{
action = creator( ...args )
}
catch( e ){
action = {type: NEW_THROWN_ERR, error: true, payload: serializeError(e) }
}
finally{
return action // eslint-disable-line no-unsafe-finally
}
}
}
return objMap(actions, actionCreator => bindActionCreators( process( actionCreator ), dispatch ) )
}
getMapStateToProps() {
return () => {
return Object.assign({}, this.getSystem())
}
}
getMapDispatchToProps(extras) {
return (dispatch) => {
return deepExtend({}, this.getWrappedAndBoundActions(dispatch), this.getFn(), extras)
}
}
}
function combinePlugins(plugins, toolbox, pluginOptions) {
if(isObject(plugins) && !isArray(plugins)) {
return merge({}, plugins)
}
if(isFunc(plugins)) {
return combinePlugins(plugins(toolbox), toolbox, pluginOptions)
}
if(isArray(plugins)) {
const dest = pluginOptions.pluginLoadType === "chain" ? toolbox.getComponents() : {}
return plugins
.map(plugin => combinePlugins(plugin, toolbox, pluginOptions))
.reduce(systemExtend, dest)
}
return {}
}
function callAfterLoad(plugins, system, { hasLoaded } = {}) {
let calledSomething = hasLoaded
if(isObject(plugins) && !isArray(plugins)) {
if(typeof plugins.afterLoad === "function") {
calledSomething = true
wrapWithTryCatch(plugins.afterLoad).call(this, system)
}
}
if(isFunc(plugins))
return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething })
if(isArray(plugins)) {
return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething }))
}
return calledSomething
}
// Wraps deepExtend, to account for certain fields, being wrappers.
// Ie: we need to convert some fields into arrays, and append to them.
// Rather than overwrite
function systemExtend(dest={}, src={}) {
if(!isObject(dest)) {
return {}
}
if(!isObject(src)) {
return dest
}
// Wrap components
// Parses existing components in the system, and prepares them for wrapping via getComponents
if(src.wrapComponents) {
objMap(src.wrapComponents, (wrapperFn, key) => {
const ori = dest.components && dest.components[key]
if(ori && Array.isArray(ori)) {
dest.components[key] = ori.concat([wrapperFn])
delete src.wrapComponents[key]
} else if(ori) {
dest.components[key] = [ori, wrapperFn]
delete src.wrapComponents[key]
}
})
if(!Object.keys(src.wrapComponents).length) {
// only delete wrapComponents if we've matched all of our wrappers to components
// this handles cases where the component to wrap may be out of our scope,
// but a higher recursive `combinePlugins` call will be able to handle it.
delete src.wrapComponents
}
}
// Account for wrapActions, make it an array and append to it
// Modifies `src`
// 80% of this code is just safe traversal. We need to address that ( ie: use a lib )
const { statePlugins } = dest
if(isObject(statePlugins)) {
for(let namespace in statePlugins) {
const namespaceObj = statePlugins[namespace]
if(!isObject(namespaceObj)) {
continue
}
const { wrapActions, wrapSelectors } = namespaceObj
// process action wrapping
if (isObject(wrapActions)) {
for(let actionName in wrapActions) {
let action = wrapActions[actionName]
// This should only happen if dest is the first plugin, since invocations after that will ensure its an array
if(!Array.isArray(action)) {
action = [action]
wrapActions[actionName] = action // Put the value inside an array
}
if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapActions && src.statePlugins[namespace].wrapActions[actionName]) {
src.statePlugins[namespace].wrapActions[actionName] = wrapActions[actionName].concat(src.statePlugins[namespace].wrapActions[actionName])
}
}
}
// process selector wrapping
if (isObject(wrapSelectors)) {
for(let selectorName in wrapSelectors) {
let selector = wrapSelectors[selectorName]
// This should only happen if dest is the first plugin, since invocations after that will ensure its an array
if(!Array.isArray(selector)) {
selector = [selector]
wrapSelectors[selectorName] = selector // Put the value inside an array
}
if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapSelectors && src.statePlugins[namespace].wrapSelectors[selectorName]) {
src.statePlugins[namespace].wrapSelectors[selectorName] = wrapSelectors[selectorName].concat(src.statePlugins[namespace].wrapSelectors[selectorName])
}
}
}
}
}
return deepExtend(dest, src)
}
function buildReducer(states) {
let reducerObj = objMap(states, (val) => {
return val.reducers
})
return allReducers(reducerObj)
}
function allReducers(reducerSystem) {
let reducers = Object.keys(reducerSystem).reduce((obj, key) => {
obj[key] = makeReducer(reducerSystem[key])
return obj
},{})
if(!Object.keys(reducers).length) {
return idFn
}
return combineReducers(reducers)
}
function makeReducer(reducerObj) {
return (state = new Map(), action) => {
if(!reducerObj)
return state
let redFn = (reducerObj[action.type])
if(redFn) {
const res = wrapWithTryCatch(redFn)(state, action)
// If the try/catch wrapper kicks in, we'll get null back...
// in that case, we want to avoid making any changes to state
return res === null ? state : res
}
return state
}
}
function wrapWithTryCatch(fn, {
logErrors = true
} = {}) {
if(typeof fn !== "function") {
return fn
}
return function(...args) {
try {
return fn.call(this, ...args)
} catch(e) {
if(logErrors) {
console.error(e)
}
return null
}
}
}
function configureStore(rootReducer, initialState, getSystem) {
const store = createStoreWithMiddleware(rootReducer, initialState, getSystem)
// if (module.hot) {
// // Enable Webpack hot module replacement for reducers
// module.hot.accept("reducers/index", () => {
// const nextRootReducer = require("reducers/index")
// store.replaceReducer(nextRootReducer)
// })
// }
return store
}