Assignment 2: Anchors & Links
Released: September 28th, 2023
Due: October 11th, 2023 at 11:59 PM ET
⚠️ Do not clone the Assignment 2 stencil code until you have submitted Assignment 1 for the last time to Gradescope. We will be checking to make sure you do not submit Assignment 1 after cloning Assignment 2.
Now that we have finished making and preparing the dough for our donuts, it's time to cook them 🧑🍳! In this assignment, you will be building upon the concepts introduced in Lab 2 to add anchor and linking support to a standard node file system. At the end, you will have built a full-stack application that will allow users to create, delete and navigate with hypertext links via the frontend. You will be graded on the functionality of your backend via a test suite and the functionality of your frontend manually.
We also want to re-emphasize the 30-minute rule – if you can’t get something that appears straightforward to work in 30 minutes, it may not be your problem. Don’t be afraid to send a message in Slack or go to TA hours if something unexpected comes up.
This assignment places a heavy emphasis on the frontend, and is a heavier workload than Assignment 1: Nodes. Remember: Start early, start today, start yesterday!
Note: Assignment 2 builds on the code from Lab 2; make sure to complete Lab 2 before starting.
Backend:
AnchorCollectionConnection
and LinkCollectionConnection
Frontend:
FrontendNodeGateway.deleteNode()
TextContent.tsx
and ImageContent.tsx
handleStartLinkClick
and handleCompleteLinkClick
in NodeView.tsx
CompleteLinkModal
NodeLinkMenu
Deployment:
Graduate/Capstone Requirement:
The following architecture diagram should give you a better understanding of the new structure of our full-stack application. A few important things to note:
Note: In your next technical interview, you can refer to this project as having a monolithic frontend (AKA a single, large component) that interacts with a microservice (AKA multiple, smaller components that each perform a particular function) backend. For more information on the tradeoffs between monolithic applications and microservices, we encourage you to take a look at this article.
TypeScript/JavaScript has powerful operators that can make a programmer's life easier. Here are some tips for making your code cleaner!
donut && console.log('delicious')
is the same thing as
if (donut) {
console.log('delicious')
}
??
)It returns its right-hand side operand when its left-hand side operand is null
or undefined
, and otherwise returns its left-hand side operand. When used properly, it ensures that the variable we are assigning value to is never undefined
or null
.
// variable left-hand right-hand
const anchorId: string = currentValue ?? 'default string'
x ? y : z
)isDonut ? 'coffee' : 'milk'
is the same as writing
if (isDonut){
return 'coffee'
} else {
return 'milk'
}
===
vs. ==
==
converts the variable values to the same type before performing comparison. This is called type coercion.===
does not do any type conversion (coercion) and returns true only if both values AND types are identical for the two variables being compared.Note: Gains in performance between these two operations is negligible.
Introduced in 2020, the optional chaining operator (?.
) enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.
In the following example we need to first check for donut.frosting
before we can access the flavor
of the frosting.
let nestedProp = donut.frosting && donut.frosting.flavor
However, with optional chaining you don't have to explicitly test and short-circuit based on the state of donut.frosting
before trying to access donut.frosting.flavor
:
let nestedProp = donut.frosting?.flavor
cmd + shift + P
: VSCode commandscmd + left click
: navigate to implementation/usagecmd + P
: search by filenamecmd + F
: search current file for termcmd + shift + F
: search entire code base for termcmd + shift + L
: select all instances of selected term in the current fileoption + left click
: add additional cursor at left click locationoption + ↓
or option + ↑
: move line of code up or downNote, these commands are for Mac. Please replace all cmd
with ctrl
and all option
with alt
for Windows.
Note: The demo may take up to 30 seconds to load due to the free backend we used for hosting. Additionally, you might notice some strange behavior if there are multiple students interacting with the demo at the same time.
You can find a demo of the MyHypermedia application here.
Continuing from the lab, you will be implementing your own MongoDB
queries.
The backend portion is worth 30% of the assignment grade.
Don't forget to npm install
and add your .env
file!
Your objective for the backend assignment is to fill in the stencil code found at AnchorCollectionConnection.ts
andLinkCollectionConnection.ts
. We hope that writing these files will get you more familiar with creating your own MongoDB queries.
Tips:
nodeCollectionConnection.ts
)Once finished, you should be able to pass all unit (mock
) and end-to-end (e2e
) tests for Links
and Anchors
. Note that your Nodes
tests should still all pass.
Jest Commands:
Here are the following commands to run tests on a select group of Jest files:
npm test <file-name>
npm test findAnchorById.spec.ts
will run findAnchorById.spec.ts
npm test findanchorbyid
works too - it is case insensitivenpm test <folder-name>
npm test Anchors/Mock/AnchorCollectionConnection
npm test Anchors
will run all tests found in Anchors
npm test e2e
will run all e2e
unit testsFeel free to add your own custom methods in any of the <x>CollectionConnections
, gateways, or router files, as long as the test suite still passes.
The stencil only provides methods the TAs need to power our frontend, but if you think that your frontend implementation would benefit from another endpoint or support from the backend, feel free to add your custom code!
The frontend portion is worth 60% of the assignment grade.
In the last assignment you handled passing in props
to the TextContent
and ImageContent
components so that they would render the content. You also implemented the NodeBreadcrumb
component. The changes that you made were specific to single components rather than the overall system.
When implementing links
and anchors
we need different components to interact with each other, so in this assignment you will get more familiar with the frontend system as a whole.
FrontendLinkGateway
, FrontendAnchorGateway
, and FrontendNodeGateway
.
FrontendNodeGateway.deleteNode
TextContent.tsx
and ImageContent.tsx
Start Link
Complete Link
and design the CompleteLinkModal
NodeLinkMenu
. Ensure the menu is always up-to-date.Remember, you can search for a file in VSCode using cmd + P
(Mac) or ctrl + P
(Windows). These commands are very useful to navigate between files, which you will have to do in this assignment!
Now that we are supporting Links and Anchors, our frontend gateways have gotten more complex. FrontendNodeGateway.deleteNode()
now has to delete all links and anchors associated with the node before deleting the node itself.
Frontend gateway interacting with backend microservices
We have designed this project such that each of the backend microservices (router, gateway, collectionConnection) do not interact with one another. This means that there is absolutely no cross talk between the link, anchor and node backends.
So how do we delete nodes if the deletion of a node relies on the deletion of anchors and links as well? For the purposes of our assignmments (you can do this differently in your final project) we will be using the frontend gateways to handle the cross talk. For example, this is what happens in the frontend's FrontendNodeGateway.deleteNode()
method:
FrontendAnchorGateway
anchorIds
via FrontendLinkGateway
FrontendLinkGateway.deleteLinks()
to delete links and orphan anchorsFrontendNodeGateway
You can make calls to other gateways by importing them, for example in FrontendLinkGateway
, we already import FrontendAnchorGateway
so that we can make a call to an FrontendAnchorGateway
and delete the orphanAnchors
. You will want to do this when you are implementing FrontendNodeGateway.deleteNode()
!
import { FrontendAnchorGateway } from '../anchors'
await FrontendAnchorGateway.deleteAnchors(orphanAnchors)
TODO: Implement FrontendNodeGateway.deleteNode()
During the lab, the functions generateRandomTextAnchors
and generateRandomImageAnchors
were used to randomly generate anchors to display. Now that we have a working backend, we can instead load and display the node's anchors from the database using FrontendAnchorGateway
.
TODO: Load anchors from the database for a given node instead of randomly generating anchors in TextContent.tsx
and ImageContent.tsx
Start Link
and Complete Link
In Lab 2
you handled selecting an extent on the node that you are currently on. Once you handled the case of selecting the extent, you used setSelectedExtent
. There are a few other global state variables that are essential when implementing Start Link
and Complete Link
.
To use a global state variable, import the atom (e.g. isLinkingState
) from global/Atoms
and import useRecoilState
from recoil
.
boolean
that indicates whether we are linking from a node.const [isLinking, setIsLinking] = useRecoilState(isLinkingState)
startAnchor
: IAnchor
that indicates the particular anchor that we are linking from.endAnchor
: Indicates the anchor that we are linking to. This is important when we want to complete the link.const [startAnchor, setStartAnchor] = useRecoilState(startAnchorState)
const [endAnchor, setEndAnchor] = useRecoilState(endAnchorState)
selectedAnchors
: Indicates the list of anchors that are currently selected.const [selectedAnchors, setSelectedAnchors] = useRecoilState(selectedAnchorsState)
selectedExtent
const [selectedExtent, setSelectedExtent] = useRecoilState(selectedExtentState)
Note: If the Extent is null
then it should link the entire node. If it is undefined
then it should return an error.
In Lab 2
, we setSelectedExtent
in ImageContent.tsx
and FolderContent.tsx
respectively. SinceselectedExtentState
is an atom in Recoil, we can access and mutate the state from anywhere in our codebase!
TODO:
handleStartLinkClick
and handleCompleteLinkClick
in NodeView.tsx
and ensure that the functionality meets the expectations defined below.CompleteLinkModal
CompleteLinkModal
.CompleteLinkModal
. From this modal, the user should be able to set the link Title
and Explainer
.
ILink
interface in Types > ILink
and add additional properties to be added to the database. Make sure to change the interface in both the server and client folders if you do this.NodeLinkMenu
Now we have the ability to create links between any two Extents
on two documents (or within the same document). Your next task is to design the NodeLinkMenu
, which is where you show the links for a particular node, and handle the user interactions with that link list.
TODO: Design and implement your NodeLinkMenu
. You can should put this in src > components > NodeView > NodeLinkMenu
NOTE: See the #fixes channel on Slack for a util class that you can put in this folder!
The NodeLinkMenu
in the demo is only one possible design for this menu. Feel free to relocate it, adjust it, and make it your own, as long as it is usable, well-designed, and the functionality expectations described below are satisfied.
You will want to use onClick
when you are designing the NodeLinkMenu
component. The following code snippet is an example of how that could look.
const clickDonut = (e: React.MouseEvent, flavor: IFlavor) => {
switch(e.detail){
case 1:
console.log('Left click', flavor.sprinkles)
case 2:
console.log('Right click', flavor.dough)
}
}
return (
<div className="donut" onClick={(e) => clickDonut(e, flavor)}>
</div>
)
Note that e.detail
is how you can specify the difference between left click and right click. The above method would print the type of sprinkles
on left click, and the dough
on right click.
Don't forget to set the css cursor
property. This is an important thing to set when it comes to usability with onClick elements!
setSelectedAnchors
state variable to do this! But how do we get the anchors on either end of the link? Look at the ILink
interface and REMEMBER, we can always make calls to the FrontendAnchorGateway
.This component should be well designed and usable. We recommend creating a mockup on paper or in Adobe Xd
or Figma
. Feel free to check in with a TA to ask for feedback on your design!
loadAnchorToLinksMap
: returns a map from an anchorID
to the anchor and its linksfetchNodeFromLink
: returns the nodeId
to navigate to for a given link and set of propertiesrouter.push(nodeId)
: navigates to a given nodeId
(requires that the const router = useRouter()
hook is somewhere in the same file)includesAnchorId
: returns a boolean representing whether an anchorID
is in the selected anchorsDeployment is worth 10% of the assignment grade.
The deployment steps are the same as for Assignment 1. You can find the link to the deployment instructions here.
Reminders before deploying:
console.log()
debug statementsprodEndpoint
in endpoint.ts
with your production endpoint URL (include the trailing slash!)These features are required for graduate-level and capstone students. For students who are not capstone or 2000-level, you can complete these features for a maximum of 15 extra credit points.
Now that you have implemented anchors and links for basic image and text nodes, it's time to implement your own node types. These can be anything ranging from a PDFNode
, SpreadsheetNode
, AudioNode
, VideoNode
or any other custom node type you want to add. For rendering audio, PDFs, feel free to use any package(s) of your choice!
If you are taking this course for 2000-level/capstone credit, you are expected to create a new node type/view and add linking/anchor functionality to the view.
If you are not taking this course for 2000-level/capstone credit, you can add a new node type/view and add linking/anchor functionality to the view for extra credit.
You should briefly explain your added functionality in your README.md
so we know where to look for it.
You should fill out your README.md
with the following information:
After completing your README, you should submit your GitHub repo to the Gradescope assignment.
Please ensure your entire project is anonymous and your name is not present anywhere (including in the deploy URLs), since we use anonymous grading.
For students taking this course as a capstone or graduate level course, the assignment is out of 115 points. For students not taking this course as a capstone or graduate level course, the assignment is out of 100 points.
Task | Grade |
---|---|
No ESLint or Prettier warnings | 5 Pts |
Run npm run lint
on the server/
and client/
directories to run the linter locally and fix any issues before you submit!
Task | Grade |
---|---|
Implement AnchorCollectionConnection.ts and LinkCollectionConnection.ts |
25 Pts |
Run npm test
on the server/
directroy to run the test suite locally and fix any issues before you submit!
Task | Grade |
---|---|
Implement frontend NodeGateway.deleteNode() should delete node as well as relevant anchors and links |
10 pts - TA will verify |
Linking works as expected |
25 pts - TA will verify |
NodeLinkMenu meets expected functionality |
25 pts - TA will verify |
Rubric specifications for Linking
and NodeLinkMenu
:
Sub-rubric | Criterion |
---|---|
Methods implementation | 10 pts - correctly implemented 5 pts - partially correct 0 pts - not implemented |
Design and usability | 10 pts - clicks behave as expected, items are usable and operate as expected 5 pts - clicks are not clear, poorly designed and not easily accessible 0 pts - no design consideration, buttons are not functional |
Overall design and usability | 5 pts - TA will verify |
Task | Grade |
---|---|
Deploy backend | 5 Pts - TA will verify |
Deploy frontend | 5 Pts - TA will verify |
Task | Grade |
---|---|
Support an additional node type in your MyHypermedia app |
7.5 Pts - TA will verify |
Add linking and anchoring functionality for the additional node type that you support | 7.5 Pts - TA will verify |