Kinesim/vendor/state/State.js
Amit Kumar Nandi c51788eb87 Forgejo Up
2024-10-03 06:46:07 +05:30

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) => {
//
// })