-
Notifications
You must be signed in to change notification settings - Fork 1
Front end application
The front-end application of VisColl is built using React and Redux. The UI is made up of components that react to user interactions. On user interaction (such as a click or form submission), an action gets created and dispatched. These actions go through the middleware and gets sent to the VisColl api. Depending on the action being dispatched, either the front-end, backend or both will modify the Redux store. Originally, only the backend api contains logic to modify state. Our application has grown client-heavy in order to keep the app responsive under heavy load.
VisColl uses IndexedDB to persist a local copy of the Redux store in the user's web browser. Specifically we persist the user
subtree of the store. This prevents the user from requiring to login again once they close the web browser.
The three main containers within the app are
-
Authentication
: contains the login, registration, password reset components -
Dashboard
: contains components for the dashboard view -
Project
: contains components for the edit project view
Containers, represented as orange boxes in the diagram below, are the 'smart' components that connect to the Redux store and dispatch actions. Regular components, in blue below, are presentational components that receive data and callbacks from containers.
The store is composed of four subtrees: user
, dashboard
, active
and global
. The user
tree contains the user's account information along with its authentication token. The dashboard
tree contains a list of the user's existing projects and images. The active
tree, which gets populated when the user opens a project, contains the entire project data and relevant app states for the project edit view. The global
tree contains general states that are used across the whole app.
active: {
project: {
groupIDs : [...],
leafIDs : [...],
rectoIDs : [...],
versoIDs : [...],
Groups: {...},
Leafs: {...},
Rectos: {...},
Versos: {...},
Notes: {...},
...
}
}
The active project tree holds the data of the project that the user is currently viewing and editing.
The group, leaf, recto, verso, and note collation objects are stored in the Javascript objects active.project.Groups
, active.project.Leafs
, active.project.Rectos
, active.project.Versos
, and active.project.Notes
respectively. These are used as dictionaries, where the keys are the collation object IDs, to lookup the collation objects. The order of the groups, leaves and sides are captured by arrays of ID's: active.project.groupIDs
, active.project.leafIDs
, active.project.rectoIDs
, and active.project.versoIDs
. Note that the dictionaries of the collation objects are not necessarily ordered, they are only used to look up the objects.
Axios and three custom middleware are used in this application. They are located at viscoll-app/src/store/middleware
.
Axios is used to insert the user's authentication token into the request headers before the requests are sent over to the API. Axios is also configured to show and hide notifications, backend error messages and the loading pop-up.
This middleware listens for collation modification action types (eg adding/deleting leaves) and then calls relevant functions with logic to modify the project state. For consistency, all action types captured by this middleware are suffixed by _FRONTEND
.
This middleware modifies the redux store after receiving the payload from the backend. The action types captured by this middleware are suffixed by _BACKEND
.
This middleware is responsible for listening to user actions and creating the reverse of those actions and pushing it into the undo or redo stack.
The collation diagrams are drawn using the PaperJS framework. The files needed to draw the diagrams are under viscoll-app/src/assets/visualmode
. To create a diagram, instantiate a PaperManager and pass the necessary information to draw. Examples of this can be found in the VisualMode
and ViewingMode
components.
Collation drawing is broken down into multiple steps:
- calculate the y-positions of the leaves and groups
- draw the group
- add click listeners to the groups
- draw the leaves
- calculate indentation of leaf based on given nest level
- draw horizontal path
- if leaf is conjoined, draw vertical path to conjoin partner
- draw any visible leaf attributes (folio number, attachment, notes, etc)
- add click listeners to the leaves
Undo/redo is handled by the undoRedoMiddleware, located at viscoll-app/src/store/middleware
.
The undo and redo history are stored as lists under the history
tree in the Redux store. history.undo
and history.redo
are Javascript lists but are used as stacks. When the user hits undo or redo, we pop the latest item off the undo or redo stack and apply the item. When a user action occurs, we find the reverse of that action and push it to the undo or redo stack. The specific stack to push to depends on the context of the action. If the action is a regular user action or a redo action, the reverse of that action will be pushed to the undo stack. If the action was caused by an undo, the reverse of that action will be pushed to the redo stack.
The logic for finding the reverse actions is located in the helper files under viscoll-app/src/actions/undoRedo
. Reverse actions are still structured as regular actions that get dispatched normally. The only differentiating factors are the new fields isUndo
, isRedo
and urID
. If isUndo=true
, the action is an undo action, and similarly for isRedo
. urID
is the undo/redo ID explained in the later section below.
A reverse action of updating a leaf would be updating the leaf to its old value. However, not all actions and reverse actions are one to one. For example, the reverse of deleting a group can be made up of multiple actions: create group, create leaves, create nested groups if any, update leaves, update sides and link notes if any. Because of this, each item in the undo and redo stack is actually a list, and the list may contain one or more actions. If the item contains multiple actions, these actions will get sequentially dispatched by the middleware.
Since not all actions and reverse actions are one to one, we need a way to know which of the multiple reverse actions belong to one single main action that we are trying to undo/redo. We address this by having an id field urID
. An action and its compound reverse actions have the same urID
value.