262 lines
7.4 KiB
JavaScript
262 lines
7.4 KiB
JavaScript
define((require, exports, module) => {
|
|
/**
|
|
* thoughts:
|
|
* - objects must not be added, items can only be added to arrays
|
|
* - -> initial data is the schema
|
|
*
|
|
* store.listen([(state, getStore) => getStore('Robot').getState().angles]) // only acces other stores in the listen method
|
|
* - only acces the state in the listen method and in actions
|
|
* store.action('NAME', (state, data, getStore) => {
|
|
* getStore('Robot').dispatch('SOME_ACTION', {data}) // do not get state from other stores?
|
|
* })
|
|
*
|
|
* - dispatching actions within actions may leed to false undo. Outer action causes inner action, so only outer action has to be called
|
|
* - -> catch this case like InnerFunction = true
|
|
*
|
|
* - disable listeners, when applying undo - redo actions
|
|
*/
|
|
class Store {
|
|
constructor(state, name, storeManager) {
|
|
this.state = state
|
|
this.name = name
|
|
this.storeManager = storeManager
|
|
this.actions = {}
|
|
this.listeners = {}
|
|
this.listenerId = 1
|
|
}
|
|
|
|
action(name, ...args) {
|
|
if (this.actions.hasOwnProperty(name)) {
|
|
console.warn(`action name in use: ${name}`)
|
|
}
|
|
|
|
let callback = args[0]
|
|
|
|
// selector given
|
|
if (args.length === 2) {
|
|
const selector = args[0]
|
|
const cb = args[1]
|
|
callback = (state, data) => {
|
|
const recursivelyAssignObjects = (object, selectedObj, newValue) => {
|
|
if (object === selectedObj) {
|
|
return newValue
|
|
}
|
|
for (const key in object) {
|
|
if (object.hasOwnProperty(key)) {
|
|
// key authors object book[0]
|
|
if (object[key] === selectedObj) {
|
|
return Object.assign({}, object, {
|
|
[key]: newValue,
|
|
})
|
|
}
|
|
// key 0 object books
|
|
if (Array.isArray(object[key]) || typeof (object[key]) === 'object') {
|
|
const newObject = recursivelyAssignObjects(object[key], selectedObj, newValue)
|
|
if (newObject) {
|
|
return Object.assign({}, object, {
|
|
[key]: newObject,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
const selectedObj = selector(state)
|
|
|
|
if (typeof selectedObj !== 'object') {
|
|
console.warn(`Action selector must select an object! (${selectedObj}) selected`)
|
|
}
|
|
|
|
const newState = recursivelyAssignObjects(state, selectedObj, cb(selectedObj, data))
|
|
if (!newState) {
|
|
console.warn('selector does not match any object', selector, state)
|
|
}
|
|
|
|
return newState
|
|
}
|
|
}
|
|
|
|
this.actions[name] = callback
|
|
}
|
|
|
|
listen(...args) {
|
|
let callback
|
|
// selectros given
|
|
if (args.length === 2) {
|
|
const selects = args[0]
|
|
const cb = args[1]
|
|
const currentStates = []
|
|
|
|
for (const select of selects) {
|
|
currentStates.push(select(this.state))
|
|
}
|
|
|
|
const handleChange = (state) => {
|
|
let changed = false
|
|
for (const i in selects) {
|
|
const newState = selects[i](state)
|
|
if (newState !== currentStates[i]) {
|
|
changed = true
|
|
currentStates[i] = newState
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
cb.apply(this, currentStates)
|
|
}
|
|
}
|
|
|
|
callback = handleChange
|
|
// execute cb once on init
|
|
cb.apply(this, currentStates)
|
|
} else {
|
|
callback = args[0]
|
|
}
|
|
const id = this.listenerId++
|
|
this.listeners[id] = callback
|
|
// call once to init. Default state is already set
|
|
callback(this.state)
|
|
return id
|
|
}
|
|
|
|
unsubscribe(id) {
|
|
if (this.listeners[id]) {
|
|
delete this.listeners[id]
|
|
} else {
|
|
console.warn(`Could not find listener for id: ${id}`)
|
|
}
|
|
}
|
|
|
|
dispatch(action, data) {
|
|
this.storeManager.notify(action, data, this.name, this)
|
|
}
|
|
|
|
getState() {
|
|
return this.state
|
|
}
|
|
|
|
getStore(storeName) {
|
|
// console.log(`%cStore [${this.name}] requesting data from [${storeName}]`, 'font-weight:bold')
|
|
return this.storeManager.getStore(storeName)
|
|
}
|
|
}
|
|
|
|
class StoreManager {
|
|
constructor() {
|
|
this.state = {}
|
|
this.store = this
|
|
this.middlewares = []
|
|
this.stores = {}
|
|
}
|
|
|
|
getState() {
|
|
return this.state
|
|
}
|
|
|
|
getStore(storeName) {
|
|
return this.stores[storeName]
|
|
}
|
|
|
|
createStore(name, defaultState) {
|
|
this.state[name] = defaultState
|
|
if (this.stores[name]) {
|
|
console.error(`store ${name} already exists`)
|
|
}
|
|
const store = new Store(this.state[name], name, this)
|
|
this.stores[name] = store
|
|
return store
|
|
}
|
|
|
|
removeStore(name) {
|
|
if (!this.stores[name]) {
|
|
console.warn(`cannot find store with name: ${name}`)
|
|
} else {
|
|
delete this.stores[name]
|
|
}
|
|
}
|
|
|
|
notify(action, data, name, store) {
|
|
const newState = this.runMiddleware(action, data, store)
|
|
|
|
this.state = Object.assign({}, this.state, {
|
|
[name]: newState,
|
|
})
|
|
store.state = this.state[name]
|
|
|
|
// call listeners
|
|
for (const storeId in this.stores) {
|
|
if (this.stores[storeId]) {
|
|
for (const id in this.stores[storeId].listeners) {
|
|
if (this.stores[storeId].listeners[id]) {
|
|
this.stores[storeId].listeners[id](this.stores[storeId].state)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// todo check if state was changed in listeners
|
|
}
|
|
|
|
runMiddleware(action, data, store) {
|
|
// cant prechain the middleware, sonce the scope of getStae is matching the local store
|
|
const middlewareAPI = {
|
|
getState: store.getState.bind(store),
|
|
getGlobalState: this.getState.bind(this),
|
|
dispatch: (action, data) => store.dispatch(action, data), // allow for further dispatches in middleware //todo check this if we need to bind
|
|
}
|
|
|
|
// mid1,mid2,mid3
|
|
const chain = this.middlewares.map(middleware => middleware(middlewareAPI))
|
|
// mid1(mid2(mid3(dispatch))(name, data)
|
|
function executeAction(name, data) {
|
|
if (!this.actions.hasOwnProperty(name)) {
|
|
console.warn(`action not found: ${name}`)
|
|
}
|
|
return this.actions[name](this.state, data)
|
|
}
|
|
|
|
// mid1,mid2,mid3,dispatch
|
|
// (prev, current) (mid3, dispatch)
|
|
const runMiddleware = [...chain, executeAction.bind(store)].reduceRight((composed, f) => f(composed))
|
|
return runMiddleware(action, data)
|
|
}
|
|
|
|
applyMiddleware(...middlewares) {
|
|
this.middlewares = [...this.middlewares, ...middlewares]
|
|
}
|
|
}
|
|
const storeManager = new StoreManager()
|
|
|
|
module.exports = storeManager
|
|
})
|
|
|
|
// s.commit('CHANGE_CONTROL_MODE', mode, (state, data) => {
|
|
// return [
|
|
// ...state, {
|
|
// controlMode: data,
|
|
// },
|
|
// ]
|
|
// })
|
|
// // global state (app state)
|
|
// GlobalDataDomain.setMode(mode)
|
|
//
|
|
// function onChange(store, select, onChange) {
|
|
// let currentState;
|
|
//
|
|
// function handleChange() {
|
|
// let nextState = select(store.getState());
|
|
// if (nextState !== currentState) {
|
|
// currentState = nextState;
|
|
// onChange(currentState);
|
|
// }
|
|
// }
|
|
//
|
|
// let unsubscribe = store.subscribe(handleChange);
|
|
// handleChange();
|
|
// return unsubscribe;
|
|
// }
|
|
//
|
|
// store.onChange(store, [state, Module.getState(), Module.getClicked()](state) => {
|
|
//
|
|
// })
|