-
Notifications
You must be signed in to change notification settings - Fork 74
Design Space Architecture
Design Space runs on React.js, a library for building UI that is responsive to the changes in each component's state and only updates the parts of the DOM that need changing. Suggested application architecture for React is Flux which keeps data flowing one way, avoiding the complications two-way data binding brings to bigger web apps.
To understand how Design Space will work, it is important to understand the uni-directional data flow that Flux dictates. It is nicely summarized in this picture below:
In our case, instead of a Web API, we use Playground plugin for Photoshop. Playground plug-in allows Photoshop to display an HTML page within its window, and also allows for JavaScript calls to get data out of Photoshop and make calls to execute PS functionality.
It is important to first understand that the Design Space UI runs synchronously. That is, each call made will immediately try and update the UI. We will explain below how the asynchronous calls fit into this architecture.
Below is a short description of each part of Flux and how it fits in the Design Space architecture.
React Views are components which make up the UI of Design Space. Each component is made up of HTML elements and other React components, and will have props and a state. Both props and state define characteristics of how that component will be rendered. The subtle difference being that props are defined by the creating code and state is changed by the component itself as a result of user actions.
A view can subscribe to multiple stores and build its state from the information sent from those stores. Stores will be described in more detail later on.
Action creators contain the application logic of Design Space, and contain all interaction we have with Spaces Adapter code. They can be driven either by an event emitted from Spaces or user interacting with one of the views, which in turn may communicate with Photoshop for a certain action.
After an action creator is done with the requested action, it will create an action object that contains a payload and send it to the dispatcher.
From Flux architecture page:
The dispatcher is a singleton, and operates as the central hub of data flow in a Flux application. It is essentially a registry of callbacks, and can invoke these callbacks in order. Each store registers a callback with the dispatcher. When new data comes into the dispatcher, it then uses these callbacks to propagate that data to all of the stores.
At first Dispatcher may seem redundant, however, multiple stores can request to listen to the same action and may be dependent on each others. Dispatcher is useful in these instances for stores to wait for each other to finish acting on the payload from an action and provides a central organization mechanism.
Stores are where our understanding of the state of Photoshop is kept. They store the data that drives the UI, and change that data in response to payloads coming from the dispatcher. Stores can emit changed events which will notify all view components that are listening to them to update their own state and cause React to re-render them.
For describing how a UI action goes through Design Space, we're going to use the example of selecting a layer.
The component that renders the layer UI (src/js/jsx/sections/layers/LayerFace.jsx
) has a handler for click events (onClick={this._handleLayerClick}
). LayerFace#_handleLayerClick
sends a request to the layers Action Creator (this.getFlux().actions.layers.select
) with pertinent parameters (document ID, layer ID, and modifiers [if any]).
At this point the action creator splits the request into two pieces. The first part is a synchronous call within the Flux mechanism to update the document store with the layer selection details and re-render the React UI. The second part is an asynchronous call to Photoshop in an attempt to modify the Photoshop document state.
For the first case, actions.layers.select
(in src/js/actions/layers.js
) immediately sends an action (events.document.SELECT_LAYERS_BY_ID
) to the dispatcher stating that layer is selected. This gets dispatched to stores with the select layer payload. The net effect here is that the rest of the Playground application state & logic is notified of this layer selection change and updates itself accordingly. More on this later.
Then for the second case, the action creator uses Space Adapter
's layerLib.select
(in Spac-Adapter/src/lib/layer.js
) to make a Photoshop-appropriate payload which it then issues to Photoshop using descriptor.playObject
.
There are two possible outcomes to the asynchronous call into Photoshop. But first, let's go over how the dispatch
call above is handled within Flux.
The DocumentStore
(src/js/stores/document.js
) holds the document state, including the current set of selected layers. The store also contains a handler (DocumentStore#_handleLayerSelectByID
) bound to the event emitted above (events.document.SELECT_LAYERS_BY_ID
). The payload
contains the selected layer specifics, so the document store changes this information in another payload (Immutable.Set(payload.selectedIDs
) and then calls DocumentStore#_updateLayerSelection
. DocumentStore#_updateLayerSelection
emits a change event (emit("change")
) to re-render (if needed) the React components that listen to the store.
By default, a React component will update (by way of calling its render
method) whenever its (internal) state or its (external) properties are changed. (One can also specify a shouldComponentUpdate
routine to express fine control over whether a view should be updated.)
So by whatever means, the emitted change event leads the system to require the layer UI to be updated. The layer UI then pulls updated state from necessary store(s) and re-renders.
If we weren't communicating with Photoshop, this would be the end of the life cycle. However, we made an asynchronous call above to select a layer in Photoshop. There are two possible outcomes:
The layers action creator may send another action to the dispatcher stating that select
is successful. (e.g., we requested more information from Photoshop like Layer descriptor, so we can attach more to the payload for stores to read up on and update their data.) This causes another pass through the Flux mechanism to update the React UI.
Layers action creator percolates this failure up to FluxController
(src/js/fluxcontroller.js
), and the controller will empty the action queue, reset each action receiver and store to their original state. These will eventually bounce the UI back to its original state.