-
Notifications
You must be signed in to change notification settings - Fork 74
History Subsystem
A description of the history store and how to call actions that update it appropriately.
For each document, the history store maintains a list of known past and future history states. If the state was created within Design Space, the history store also captures a snapshot of the Document
and DocumentExport
models at that point in time. When performing an undo/redo to a particular state, if the document is cached it will be loaded into the document store. Otherwise, in the case of history states created in standard photoshop or after some failure which caused the history store to be flushed, and therefore not containing a cached document snapshot, updateDocument
is called to obtain a fresh model.
Example: An on-canvas layer move. In these cases get two events: the specific change event, and a historyState
event. When handling the specific event (e.g. "move" event), unless it is guaranteed that the historyState event will be handled first, then you must use the "amendRogue" flag in the payload or use the helper action newHistoryStateRogueSafe
to create the state. This will allow the history state to be amended if it was the result of a rogue event, or created otherwise.
Using "move guide" as an example because it is initiated by PS, and DS finds out about it via an event which it handles:
var payload = {
documentID: document.id,
guide: guide,
index: index,
history: {
newState: true,
amendRogue: true
}
};
return this.dispatchAsync(events.document.history.GUIDE_SET, payload)
.bind(this)
.then(function () {
return this.transfer(resetGuidePolicies);
});
Here the action can, independent of calling photoshop, emit a single flux event which is handled by both the document store and the history store.
An example is deleting an export asset:
var payload = {
documentID: documentID,
layerIDs: layerIDs,
assetIndex: assetIndex,
history: {
newState: true,
name: strings.ACTIONS.MODIFY_EXPORT_ASSETS
}
};
return this.dispatchAsync(events.export.history.DELETE_ASSET, payload);
The action makes a change in photoshop but then has to parse the play results, or perform some separate "queries" (eg, resetBounds), before updating our model accordingly. A history state should be created as early as possible (such as with the convenience action newHistoryState()
), and then the subsequent events should amend the history state. Note that resetBounds
, resetLayers
and resetIndex
all amend history in this way.
Rotating a set of layers is an example because this will cause the bounds to change:
var documentRef = documentLib.referenceBy.id(document.id),
layerRef = layers.map(function (layer) {
return layerLib.referenceBy.id(layer.id);
})
.unshift(documentRef)
.toArray(),
rotateObj = layerLib.rotate(layerRef, angle),
options = {
historyStateInfo: {
name: strings.ACTIONS.ROTATE_LAYERS,
target: documentLib.referenceBy.id(document.id)
}
},
historyPromise = this.transfer(historyActions.newHistoryState, document.id,
strings.ACTIONS.ROTATE_LAYERS),
playPromise = locking.playWithLockOverride(document, layers, rotateObj, options);
return Promise.join(historyPromise, playPromise)
.bind(this)
.then(function () {
var descendants = layers.flatMap(document.layers.descendants, document.layers);
return this.transfer(layerActions.resetBounds, document, descendants);
});
The history store listens to ALL events with events.document.history
and events.export.history
. By default, it will amend the current history state with the current Document. By supplying a history
property on the payload, you can cause the history store to create a new history state while handling the event (by supplying a subprop history.newState
with truthy value). Additionally, the property history.amendRogue
will cause the history store to inspect the current history state, and amend it IFF it was created roguely.
A note about historyState events: We currently receive historyState events in most cases. In some cases for scenario 2 and 3, we have lazily relied on the historyState event to increment the history store. For PS reasons, we may soon move away from getting these events if they are a result of an explicit descriptor played through DS. So, we are trying to phase out these code paths in DS. This has the added benefit of providing a more positive/declarative workflow; easier to reason what's going on.
A note about historyStateInfo
play option: See SpacesExtension.js
for more details. This play option is used to explicitly combine several play commands into one single history state. Example:
playOptions = {
historyStateInfo: {
name: strings.ACTIONS.APPLY_TEXT_STYLE,
target: documentLib.referenceBy.id(currentDocument.id)
}
};
Not all properties of the DS document model should be loaded during history navigation (undo/redo). Layer selection and visibility are both fetched from Photoshop during history navigation, because it is non-trivial to know how Photoshop will apply them. Guide and Smart Guide visibility are propagated from the current state and clobber the cached state values.