src/Access.js
import has from "lodash.has";
const _impl = Symbol('impl');
/**
The base class for `Shadow.$()` method used for obtaining f.lux contextual information
concerning a non-primitive shadow state property.
The most commonly used methods include:
<ul>
<li>{@link Access#dotPath}</li>
<li>{@link Access#property}</li>
<li>{@link Access#pid}</li>
<li>{@link Access#state}</li>
<li>{@link Access#store}</li>
<li>{@link Access#waitFor}</li>
</ul>
This class provides access to the experimental {@link Property} checkpointing api. Checkpointing
allows the state to be recorded at a point in time and then later reset to that point. This is
handy when a form may accept changes and then allow the user to cancel the edit session.
@see {@link Shadow}
*/
export default class Access {
constructor(impl) {
this[_impl] = impl;
}
/**
Get the backing {@link ShadowImpl} instance for this shadow property.
@return {ShadowImpl}
*/
__() {
return this[_impl];
}
/**
Copies the current actual state for later reset using {@link Property#resetToCheckpoint}. An existing
checkpoint will take precedence over subsequent calls.
*/
checkpoint() {
this.property().checkpoint();
}
/**
Clears an existing checkpoint created using {@link Property#checkpoint}.
*/
clearCheckpoint() {
this.property().clearCheckpoint();
}
/**
Gets the path from root property using a dot (`.`) separator. Suitable for using with the lodash `result()`
function.
@return {string} path with each component joined by a `.`
@see https://lodash.com/docs/4.17.4#result
*/
dotPath() {
return this[_impl].dotPath();
}
/**
Gets the checkpoint state previously recorded using {@link Property#checkpoint}.
@return {Object|Array} the checkpoint data if checkpoint is set.
*/
getCheckpoint() {
return this.property().getCheckpoint();
}
/**
Gets if an existing checkpoint has be created using {@link Property#checkpoint}.
@return {boolean} true if a checkpoint has been recorded.
*/
hasCheckpoint() {
return this.property().hasCheckpoint();
}
/**
Gets if the shadow state property is proxying the current actual state contained in the f.lux store.
@return {boolean} `true` if actively proxying the state.
*/
isActive() {
return this[_impl].isActive();
}
/**
Gets if the property allows for assignment through the shadow state, ie `todo.desc = "go skiing"`. The
readonly attribute is hierarchically determined through the parent property if not explicitly set.
@return {boolean} - true if assignment is not allowed
*/
isReadonly() {
return this.property().isReadonly();
}
/**
Gets if the shadow property has experienced a mutation action. This method will return `true` and
{@link Access#isActive} may also return true if the f.lux store has not yet been transitioned to
the next state.
@return {boolean} `true` if the state has not been changed.
*/
isValid() {
return this[_impl].isValid();
}
/**
Gets the current shadow state for this property. The shadow state referenced by this access object
could be stale if a javascript closure has a previous reference and then performed an asynchronous
operation like a network request.
@return {Shadow}
*/
latest() {
const impl = this[_impl].latest();
return impl && impl.shadow();
}
/** @ignore */
merge(data) {
this[_impl].merge(data);
}
/**
Gets the next actual state for this property following any pending actions.
@return {Object|Array}
*/
nextState() {
return this[_impl].nextState();
}
/**
Gets the {@link Property#name} components from the root property to this property.
@return {[]} array where each name component is either a `string` or `number` depending on the
each parent component's type.
*/
path() {
return this[_impl].path();
}
/**
Gets the unique f.lux ID for this property.
@return {number} the id
*/
pid() {
return this.property().pid();
}
/**
Gets the {@link Property} managing this shadow property.
@return {Property}
*/
property() {
return this[_impl].property();
}
/**
Replaces the current property state with a checkpoint state previously recorded using
{@link Property#checkpoint}. The checkpoint is cleared.
*/
resetToCheckpoint() {
this.property().resetToCheckpoint();
}
/**
Gets the top-level shadow from the {@link Store} containing this shadow state property.
@return {Shadow}
*/
rootShadow() {
return this.property().rootShadow();
}
/**
Gets the path from root property using a slash (`/`) separator.
@return {string} path with each component separated by a `/`
*/
slashPath() {
return this[_impl].slashPath();
}
/**
@return
*/
shadow() {
return this[_impl].shadow();
}
/**
Gets the actual state being shadowed.
@return {Object|Array}
*/
state() {
return this[_impl].state();
}
/**
Gets the {@Link Store} containing the application state.
@return {Store}
*/
store() {
return this.property().store();
}
/**
Performs an update on the state values.
The callback should be pure and have the form:
```
callback(nextState) : nextState
```
- or -
```
callback(nextState) : { name, nextState }
```
The `nextState` parameter is a javascript object (not a `Shadow`) that represents the next state starting
from the current state and having all the actions in the current tick applied to it before this `update()`
call.
The callback should return an object with the following properties:
<ul>
<li> `name` - a stort moniker identifying the update call purpose. This will passed to the store middleware.
This value is optional with the default value being '[path].$().update()'.</li>
<li>`nextState` - the value for the next state after the update functionality.</li>
</ul>
*/
update(callback) {
this[_impl].update( next => {
var result = callback(next);
result = has(result, "nextState") ?result :{ nextState: nexresultt };
// mark as a replacement (expensive but conservative) since very unlikely a caller through the access
// variable will have made all the book keeping updates and no way of knowing how deep their changes
// were in the object hierarchy.
return {
name: result.name || `${ this.slashPath() }.$().update()`,
nextState: result.nextState,
replace: true
};
})
}
/**
Registers a **one-time** no argument callback to be invoked after the next {@link Store} state change.
@param {function(shadow: Shadow)} callback - a callback to be invoked after all pending changes have been
reflected in the shadow state.
*/
waitFor(callback) {
this.store().waitFor( () => callback && callback(this.latest()) );
}
}