--- title: Assignment 2 | Anchors & Links tags: assignment --- <span style="font-size: 50px;">**Assignment 2: Anchors & Links**</span> :::info **Released: September 28th, 2023** **Due: October 11th, 2023 at 11:59 PM ET** ::: :::danger ⚠️ **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. ::: <!-- INFO BOXES :::success GREEN This is for any step by step instructions that students should follow. ::: :::info BLUE This is for important assignment information throughout the assignment: **Released: September 8th, 6:00pm ET** **Due: September 15th, 11:59pm ET** ::: :::warning YELLOW Use this info box for disclaimers. ::: :::danger Use this info box for important points that students should not miss! ::: :::spoiler Dropdown list Use this ::: --> <!-- TYPESCRIPT CODE BLOCKS ```typescript const list = [10, 20]; console.log(list.map(x => (x * x))) ``` --> <!-- HOW TO CHANGE COLOR IN MARKDOWN <span style="background:aliceblue">some text with a **lightblue** background</span> <span style="color:red">some **red** text</span> --> <!-- These are a list of shortcuts available. --> *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium *[NPM]: Node Package Manager *[IDE]: Integrated Development Environment *[MERN]: MongoDB, Express, React, NodeJS *[Yarn]: Yet Another Resource Negotiator # Introduction 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. :::warning 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!** :::success **Note**: Assignment 2 builds on the code from Lab 2; make sure to complete Lab 2 before starting. ::: ## Checklist **Backend:** - [ ] Implement `AnchorCollectionConnection` and `LinkCollectionConnection` **Frontend:** - [ ] Implement `FrontendNodeGateway.deleteNode()` - [ ] Load anchors from the database instead of randomly generating anchors in `TextContent.tsx` and `ImageContent.tsx` - [ ] Implement `handleStartLinkClick` and `handleCompleteLinkClick` in `NodeView.tsx` - [ ] Design the `CompleteLinkModal` - [ ] Design and implement the `NodeLinkMenu` **Deployment:** - [ ] Deploy the backend to Render - [ ] Deploy the frontend to Vercel **Graduate/Capstone Requirement:** - [ ] Implement additional node type - [ ] Add anchoring and linking functionality for new node type # Architecture 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: - Additional functionality is added to our application through more backend services that do not depend on each other. - Our frontend is a single application that consumes all of our backend services. - Although all of our MongoDB collections belong to the same cluster, each collection is entirely independent of the other collections in the cluster. **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](https://www.n-ix.com/microservices-vs-monolith-which-architecture-best-choice-your-business/). ![](https://i.imgur.com/maObNjf.png) ## Tips and essential shortcuts for large codebases ### TypeScript Tips TypeScript/JavaScript has powerful operators that can make a programmer's life easier. Here are some tips for making your code cleaner! 1. #### **Logical AND (&&) operator** ```typescript= donut && console.log('delicious') ``` is the same thing as ```typescript= if (donut) { console.log('delicious') } ``` 2. #### **Nullish coalescing operator (`??`)** 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`. ```typescript= // variable left-hand right-hand const anchorId: string = currentValue ?? 'default string' ``` 3. #### **Conditional ternary operator** (`x ? y : z`) **`isDonut ? 'coffee' : 'milk'`** is the same as writing ``` if (isDonut){ return 'coffee' } else { return 'milk' } ``` 4. #### **`===` 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. 5. #### **Optional Chaining** 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` ### Essential VSCode shortcuts - `cmd + shift + P`: VSCode commands - `cmd + left click`: navigate to implementation/usage - `cmd + P`: search by filename - `cmd + F`: search current file for term - `cmd + shift + F`: search entire code base for term - `cmd + shift + L`: select all instances of selected term in the current file - `option + left click`: add additional cursor at left click location - `option + ↓` or `option + ↑`: move line of code up or down Note, these commands are for Mac. Please replace all `cmd` with `ctrl` and all `option` with `alt` for Windows. # Demo :::warning 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**](https://assignment-two-demo.vercel.app/). <!-- ### Demo Specs **Node Functionality** - Node should preserve all functionality outlined in Assignment 1 (add, delete, move). - Additionally, the `Folder` node should support different viewspecs (`listView` and `gridView`). **Anchor Functionality** - Create an anchor upon clicking `Start Link` button. Created anchor becomes identifiable in the node immediately. - When no region/text is selected, anchor is created with `null` as its `Extent`, which represents the whole node. - Upon clicking an anchor, the related link in link list is highlighted. - Image node anchor should contain the information for selected region. - Text node anchor should contain the selected text and its start/end index. **Link Functionality** - Supports linking from an anchor to another anchor. Link list is updated immediately. - Supports linking from an anchor to an anchor on the same node. - User can stop linking process after initiating. - On single click, link should highlight all related anchors in current node. - On double click, link should navigate to the node that contains other anchor. - Supports deleting a link. Link list is updated immediately. --> # Backend Continuing from the lab, you will be implementing your own `MongoDB` queries. :::warning **The backend portion is worth 30% of the assignment grade.** ::: :::danger **Don't forget to `npm install` and add your `.env` file!** ::: ## To Dos Your objective for the backend assignment is to fill in the stencil code found at `AnchorCollectionConnection.ts` and`LinkCollectionConnection.ts`. We hope that writing these files will get you more familiar with creating your own MongoDB queries. :::info **Tips:** 1. We highly recommend looking at the Node version of these files as a close reference for the Anchor/Link versions (e.g. `nodeCollectionConnection.ts`) 2. If you are ever confused about how we have set up the MongoDB environment, please look at the Lab 2 handout for an explanation. ::: ## Running Tests 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. :::info **Jest Commands:** Here are the following commands to run tests on a select group of Jest files: - Running a single file: `npm test <file-name>` - e.g. `npm test findAnchorById.spec.ts` will run `findAnchorById.spec.ts` - You can remove the file type suffix, so `npm test findanchorbyid` works too - it is case insensitive - Running a folder: `npm test <folder-name>` - e.g. `npm test Anchors/Mock/AnchorCollectionConnection` - e.g. `npm test Anchors` will run all tests found in `Anchors` - e.g. `npm test e2e` will run all `e2e` unit tests ::: ## Custom Methods Feel 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! # Frontend :::warning **The frontend portion is worth 60% of the assignment grade.** ::: ## Introduction 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. ### Frontend Roadmap - Make sure that you've completed **Lab 2**. It's important to understand extents before you begin this assignment. - Read and understand the implementation of`FrontendLinkGateway`, `FrontendAnchorGateway`, and `FrontendNodeGateway`. - You will be using methods from these files in the rest of the assigment. - Implement `FrontendNodeGateway.deleteNode` - Load anchors from the database instead of randomly generating anchors in `TextContent.tsx` and `ImageContent.tsx` - Implement the ability to `Start Link` - Implement the ability to `Complete Link` and design the `CompleteLinkModal` - Design your `NodeLinkMenu`. Ensure the menu is always up-to-date. ### Folder Structure ![](https://i.imgur.com/Z8VbgHu.png) 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! ## Gateways 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. :::warning **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: 1. Make request to get anchors by nodeId via `FrontendAnchorGateway` 2. If step 1 was successful, make request to get links by `anchorIds` via `FrontendLinkGateway` 3. If step 2 was successful, call frontend `FrontendLinkGateway.deleteLinks()` to delete links and orphan anchors 4. If step 3 was successful, make the request to delete node via `FrontendNodeGateway` ::: 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) ``` :::success **TODO:** Implement `FrontendNodeGateway.deleteNode()` ::: ## Loading Anchors 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`. :::success **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`. ### App States - **isLinking**: `boolean` that indicates whether we are linking from a node. ``` const [isLinking, setIsLinking] = useRecoilState(isLinkingState) ``` ### Link States - **`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) ``` ### Anchor States - **`selectedAnchors`**: Indicates the list of anchors that are currently selected. ``` const [selectedAnchors, setSelectedAnchors] = useRecoilState(selectedAnchorsState) ``` - **`selectedExtent`** ``` const [selectedExtent, setSelectedExtent] = useRecoilState(selectedExtentState) ``` :::info 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. Since`selectedExtentState` is an atom in Recoil, we can access and mutate the state from anywhere in our codebase! :::success **TODO:** - Implement `handleStartLinkClick` and `handleCompleteLinkClick` in `NodeView.tsx` and ensure that the functionality meets the expectations defined below. - Design the `CompleteLinkModal` ::: ### Functionality Expectations 1. You should be able to start a link from the currently selected extent of any node to go into linking mode. 2. It should be clearly indicated when the user is in linking mode. - There are no TODOs which indicate where or how to indicate this mode - we leave that up to you. 2. There should be a way to cancel / escape linking mode. - Again, it's up to you how you'd like to implement this. 4. When you are in linking mode, you should be able to complete the link based on the currently selected extent. This should open up a `CompleteLinkModal`. 5. Design the `CompleteLinkModal`. From this modal, the user should be able to set the link `Title` and `Explainer`. - If you want, you can also change the `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. 6. Make requests via the frontend gateways so that the anchors and links are succesfully created and in the database. ## `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. :::success **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! ::: :::info 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. ::: ### Click Events 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. ```typescript= 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. :::info Don't forget to set the `css cursor` property. This is an important thing to set when it comes to usability with onClick elements! ::: ### Functionality Expectations 1. **You should be able to select an item in this menu, which selects the anchors in the currentNode.** Think about how we can use the `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`. 2. **You should be able to follow a link.** This means that you should fetch the respective node on the other end of the link and navigates to that node 3. **You should be able to delete links and delete anchors.** (You should have a "Delete Link" button and a "Delete Anchor" button.) You should make gateway calls to the AnchorGateway and LinkGateway to do this! 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! ### Helper functions you may find useful - **`loadAnchorToLinksMap`**: returns a map from an `anchorID` to the anchor and its links - **`fetchNodeFromLink`**: returns the `nodeId` to navigate to for a given link and set of properties - **`router.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 anchors # Deployment :::warning **Deployment 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**](https://hackmd.io/1Leavzo7RzSiWVBsIs154w). :::info **Reminders before deploying**: - Don't forget to remove `console.log()` debug statements - Remember to replace the `prodEndpoint` in `endpoint.ts` with your production endpoint URL (include the trailing slash!) - Remember to make your project names and deployment URLs anonymous (do not include your name/username) ::: # Graduate/Capstone Requirement :::warning **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. # Handin You should fill out your `README.md` with the following information: - Notable design choices - Including any packages that you used. - Frontend Vercel URL - Backend Render URL - Known bugs - Only report bugs related to what you have implemented. We will be more lenient with grading if bugs are reported! - Time taken - Master's/Capstone Requirement or Extra Credit (if applicable) After completing your README, you should submit your GitHub repo to the Gradescope assignment. :::danger Please ensure your entire project is anonymous and your name is not present anywhere (including in the deploy URLs), since we use anonymous grading. ::: # Grading Breakdown 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. ## Linting - 5 Pts (autograded) | 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! ## Backend Tests - 25 Pts (autograded) | 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! ## Frontend Functionality - 60 Pts (TA will grade) | 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<br/>5 pts - partially correct </br> 0 pts - not implemented | | Design and usability | **10 pts** - clicks behave as expected, items are usable and operate as expected </br> 5 pts - clicks are not clear, poorly designed and not easily accessible </br> 0 pts - no design consideration, buttons are not functional | | Overall design and usability | **5 pts** - TA will verify | ## Deployment - 10pts (TA will grade) | Task | Grade | | ----------- | ----------- | | Deploy backend | **5 Pts** - TA will verify | | Deploy frontend | **5 Pts** - TA will verify | #### Total = 100 pts ## Graduate/Captone Requirement - 15pts | 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 |