1. Overview
Mark is designed for those who prefer to use a desktop application for managing bookmarks as well as tasks related to web pages. More importantly, it is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI).
Mark is morphed from an existing desktop Java application Address Book (Level 3) and several enhancements were made to it over a period of 8 weeks by my team of 5 software developers. The following sections document all the contributions that I have made to Mark.
2. Summary of Contributions
Here is a summary of my personal contributions to the team project over the 8 weeks. All my code contributions can be found here.
2.1. Major Enhancement: Undo/Redo a specific number of previous commands
-
What it does: This feature allows the user to undo a given number previous undoable commands (i.e. commands that alter the state of bookmarks or reminders). Previous undo commands can also be reversed by specifying the number of actions the undo command has undone. A list of actions undone/redone by the user will also be shown to the user as a command result displayed in the result display box after each undo/redo command.
-
Justification: This feature improves Mark significantly because a user can make mistakes in commands and the application should provide a convenient way to rectify them. In addition, allowing users to specify the number of actions to undo/redo greatly improves users' productivity as they can now undo/redo multiple actions using one command instead to typing undo/redo repeatedly.
-
Highlights: This enhancement affects existing commands and commands to be added in future. An in-depth analysis of design alternatives was required. The implementation too was challenging as it required changes to existing commands.
-
Credits: The basic idea is adapted from AddressBook (Level 4)
2.2. Minor Enhancements
-
Added a
goto
command that can immediately open a bookmark in the embedded web view of the app -
Added support for opening any bookmark by double clicking the bookmark in either bookmark list, folder tree view or favorite bookmark list in the GUI
-
Added a
favorite
command that can add an existing bookmark to the list of favorite bookmarks and a separate list of of favorite bookmarks to the dashboard tab of the app -
Improved the existing
add
command to support bookmarking current web page by supplying a keywordthis
in place of theURL
field -
Improved the existing
find
command to support filtering bookmarks by tags and/or folders
2.3. Other Contributions
-
Project management:
-
Set up the team organisation and team repo
-
Managed issue tracker and milestones for team repo
-
Managed release
v1.3
on GitHub
-
-
Documentation:
-
Migrated Developer Guide from Address Book to Mark and added the user stories: #7
-
-
Community:
-
Tools:
-
Integrated several CI tools (Travis, AppVeyor) to the team repo
-
Added additional tools (Codacy, Netlify and Coveralls) to the team repo
-
Added some useful GitHub plugins (WIP bot, PR Triage) to the team repo
-
3. Contributions to the User Guide
I wrote about the usage of goto
, favorite
, undo
and redo
commands for the User Guide.
Given below are sections I contributed to the User Guide.
They showcase my ability to write documentation targeting end-users.
3.1. Opening a bookmark: goto
This command opens the specified the bookmark from the bookmark manager.
Format: goto INDEX
For example:
-
list
goto 1
Opens the first bookmark in the bookmark manager.
Parameter constraints:
3.2. Adding a bookmark to Favorites: favorite|fav
This command adds a Favorite
tag to the specified bookmark.
Format: favorite|fav INDEX
For example:
-
If you want to add the first bookmark in the bookmark list to your favorite bookmarks, simply input
favorite 1
, aFavorite
tag will be added to the fist bookmark and the bookmark will also appear in the favorite bookmark list in the dashboard. -
Alternatively, you can also type a shorter command
fav 1
to add the first bookmark to your favorite bookmarks as well.
Parameter constraints:
3.3. Undoing previous commands: undo
If you mistakenly enter a command that permanently changes Mark, do not worry, you can always use undo
to
rectify the mistake! This command restores Mark to the state before the given number of previous undoable commands were
executed.
Format: undo [STEP=1]
For example:
-
When you accidentally delete a wrong bookmark and you want to bring it back. There is no need for you to manually add that bookmark back as
undo
will do the magic for you. Just inputundo
in the command box, the deleted bookmark will reappear in your bookmark list. -
Suppose you have entered two
delete
commands,delete 1
anddelete 2
, and now you want to get back both bookmarks, a possible way is enterundo
twice. Besides, Mark also offers an alternative where you can just typeundo 2
to undo these two commands.
Parameter constraints:
3.4. Redoing previously undone commands: redo
This command reverses the given number of undone actions.
Format: redo [STEP=1]
Examples:
-
redo
Reverses the most recentundo
command. -
delete 1
delete 2
undo 2
redo 2
Theredo 2
command restores the bookmark list to the state before theundo 2
command was executed. You will see the two bookmarks you just recovered get deleted again.
Parameter constraints:
4. Contributions to the Developer Guide
I modified the section about the logic component in the Developer Guide and added the description of the architecture and some use cases of Undo/Redo feature to the Developer Guide as well. Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.
4.1. Logic component
The following class diagram illustrates the structure of the Logic
component:
API :
Logic.java
Logic
uses the MarkParser
class to parse the user command.
This results in a Command
object which is executed by the LogicManager
.
The command execution can affect the Model
(e.g. adding a bookmark) and/or Storage
(e.g. import bookmarks).
The result of the command execution is encapsulated as a CommandResult
object which is passed back to the Ui
.
In addition, the CommandResult
object can also instruct the Ui
to perform certain actions,
such as displaying help to the user, switching the tab view, or exiting the application.
As an example of how the Logic
component works, the following sequence diagram shows the interactions
within the Logic
component for the execute("delete 1")
API call:
delete 1
Command
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
|
The Logic
component does the following:
-
Parses and executes user commands
-
Modifies
Model
and/orStorage
according to user commands -
Instructs
Ui
throughCommandResult
objects
4.2. Undo/Redo feature
The Undo and Redo feature is crucial to give users the flexibility of reverting wrongly executed commands. This section explains the implementation of the undo/redo mechanism as well as some design considerations when implementing this feature.
4.2.1. Implementation
The undo/redo mechanism is facilitated by VersionedMark
.
It extends Mark
with an undo/redo history, stored internally as a list of markStateRecord
and currentPointer
.
The class diagram for MarkStateRecord
and its relationship with ReadonlyMark
, Mark
and VersionedMark
is shown below:
As shown in the class diagram, VersionedMark
implements the following operations:
-
VersionedMark#save(String record)
— Saves the current Mark state and the corresponding record to its history. -
VersionedMark#undo(int step)
— Undoes the given number of actions and returns the record which consists of the list of actions being undone. -
VersionedMark#redo(int step)
— Redoes the given number actions and returns the record which consists of the list of actions being undone.
These operations are exposed in the Model
interface as Model#saveMark(String record)
, Model#undoMark(int step)
and Model#redoMark(int step)
respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedMark
will be initialized with the initial
Mark state and an empty record, and the currentPointer
pointing to that single state record.
Step 2. The user executes delete 5
command to delete the 5th bookmark in the Mark. The delete
command calls
Model#saveMark(String record)
, causing the modified state of the Mark and the record of the command after command execution
to be saved in the markStateRecords
, and the currentPointer
is shifted to the newly inserted state record.
Step 3. The user executes add u/www.google.com …
to add a new bookmark. The add
command also calls
Model#saveMark(String record)
,
causing another modified Mark state and the record to be saved into the markStateRecords
, and the currentPointer
is
again shifted to the newly inserted state record..
If a command fails its execution, it will not call Model#saveMark(String record) , so the Mark state will not be saved into the
markStateRecords .
|
Step 4. The user now decides that adding the bookmark was a mistake, and decides to undo that action by executing the
undo
command. The undo
command will call Model#undoMark(1)
since by default the number of steps
to undo is 1 if not specified. This command shifts the currentPointer
once to the left,
pointing it to the previous state record, and restores the Mark to that state.
If the currentPointer is at index 0, pointing to the initial Mark state, then there are no previous Mark states to
restore.
The undo command uses Model#canUndoMark(1) to check the if there are enough commands to undo.
In this case, it will return an error to the user rather than attempting to perform the undo.
|
The following sequence diagram shows how the undo operation works:
The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML,
the lifeline reaches the end of diagram.
|
The redo
command does the opposite — it calls Model#redoMark(int step)
, which shifts the currentPointer
to the right by the given number of steps,
and restores the Mark to the state corresponding to the currentPointer
.
If the currentPointer is at index markStateRecords.size() - 1 , pointing to the latest Mark state, then there
are no undone Mark states to restore. The redo command uses Model#canRedoMark(1) to check the if there are enough commands to redo.
In this case, it will return an error to the user rather than attempting to perform the redo.
|
Step 5. The user then decides to execute the command list
. Commands that do not modify the Mark, such as find
, will
usually not call Model#saveMark(String record)
, Model#undoMark(int step)
or Model#redoMark(int step)
. Thus, the markStateRecords
remains
unchanged.
Step 6. The user executes clear
, which calls Model#saveMark(String record)
. Since the currentPointer
is not pointing at the end
of the markStateRecords
, all state records after the currentPointer
will be purged.
We designed it this way because it no longer makes sense to redo the add u/www.google.com …
command. This is the
behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
4.2.2. Design Considerations
This section explains some of the design considerations we had when implementing this feature.
Aspect: How undo & redo executes
-
Alternative 1 (current choice): Saves the entire Mark state.
-
Pros: Easy to implement.
-
Cons: May have performance issues in terms of memory usage.
-
-
Alternative 2: Individual command knows how to undo/redo by itself.
-
Pros: Will use less memory (e.g. for
delete
, just save the bookmark being deleted). -
Cons: Must ensure that the implementation of each individual command are correct.
-
Aspect: Data structure to support the undo/redo commands
-
Alternative 1 (current choice): Use a list to store the history of Mark state records.
-
Pros: Undo/redo multiple commands can be achieved in O(1) time by just shifting the current pointer.
-
Cons: Clear state records after the current pointer can take O(n) time.
-
-
Alternative 2: Use two stacks, an undo stack and a redo stack to store the history of Mark state records.
-
Pros: Clear state records after the current pointer can be achieved in O(1) time by clearing the redo stack.
-
Cons: Undo/redo multiple commands can take O(n) time as each undo/redo action requires popping a state record from the undo stack and pushing it into the redo stack. There is no way to pop multiple state records efficiently at one time.
-
Aspect: Commands to include for undo/redo
-
Alternative 1 (current choice): Include only commands that modify the state of Mark data.
-
Pros: Only meaningful app data is stored, less memory is consumed
-
Cons: Unable to retrieve commands that do not alter the state of Mark, such as
find
,list
-
-
Alternative 2: Include all commands
-
Pros: All commands can be undone and redone
-
Cons: More data to be stored and these data might not be meaningful to the users. More data related to Command class also needs to be stored and VersionedMark might need to depend on Logic which breaks the layered architecture of the existing application
-
4.3. Use Cases for Undo/Redo
4.3.1. Use case: UC09 - Undo previous undoable commands
MSS
-
User requests to undo the previous undoable command.
-
Marks restores the list to the state before the previous undoable command was executed.
Use case ends.
Extensions
-
1a. There is no command to undo.
Mark shows an error message.
Use case ends.
4.3.2. Use case: UC10 - Redo previously undone commands
MSS
-
User requests to redo the previously undone command.
-
Marks reverses the most recent ‘undo’ command.
Use case ends.
Extensions
-
1a. There is no previous
undo
command to redo.Mark shows an error message.
Use case ends.