Merge branch 'sprint-3' into 49-image-feature

This commit is contained in:
Henry Feng 2022-12-01 19:39:06 -08:00
commit fa32a61df9
29 changed files with 726 additions and 206 deletions

View File

@ -1,29 +0,0 @@
name: Prettier
on:
pull_request:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
# Single deploy job since we're just deploying
test:
runs-on: ubuntu-latest
steps:
- name: Install apt updates
run: sudo apt -y update; sudo apt -y upgrade;
- name: Install prerequisites
uses: actions/setup-node@v3
with:
node-version: 18
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: sudo npm install
- name: Start local http server
run: sudo npm run http-server &
- name: Run tests
run: sudo npm lint-prettier

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,47 @@
# CI/CD Phase 2
## Overall Pipeline Architecture
Rather than create one large pipeline with many steps which increases complexity, we decided to create many small independent pipelines which work in parallel to conduct code quality checking. Using this strategy, if any one pipeline has issues, we can still continue development without delay, and the quality of code is likely to remain high.
## Overview of Pipeline Features
We've identified 5 major features which we definitely want to implement in the CI/CD pipeline.
- Deployment
- Unit Testing
- Linting
- End To End Validation
- Manual Validation
We also identified some features which are nice to have:
- Automatic documentation publishing
- Minification
- HTML Validation and accessibility scoring
We created this diagram to demonstrate our strategy of multiple simple pipelines.
![Pipeline Diagram](phase2.drawio.png)
## Finished Features and Implementation
So far the features listed below have been completed to some degree:
- Deployment
- Implemented: action triggered on any push to main, uses the github pages action to publish the app
- Implemented: uses JSDoc to generate documentation on the same site at [docs](https://cse110-fa22-group29.github.io/cse110-fa22-group29/docs/)
- ToDo: Add minification step between trigger and github pages action
- Unit Testing
- Implemented: action triggers on PR to main, uses mocha to perform unit testing on core components
- End to end testing
- Implemented: action triggers on PR to main, uses mocha and puppeteer to perform end to end testing
- Linting (JS)
- Implemented: action triggers on PR to main, uses eslint to perform style enforcement on all JS components
- Linting (HTML)
- Implemented: action triggers on PR to main, uses HTMLhint to perform style enforcement on all HTML components
- Linting (CSS)
- Implemented: action triggers on PR to main, uses Stylelint to perform style enforcement on all CSS components
- Linting (general)
- Implemented: action triggers on PR to main, uses Prettier to perform style checking on all file types
## Planned Features and Timeline

BIN
admin/cipipeline/phase2.mp4 Normal file

Binary file not shown.

View File

@ -0,0 +1,35 @@
# Meeting Minutes (11/29/2022)
## Team 29: Hackers1995
## Meeting Topic: Sprint 3 Debut Meeting
## Attendance
1. Rhea Bhutada
2. George Dubinin
3. Gavyn Ezell
4. Henry Feng
5. Kara Hoagland
6. Marc Reta (remote)
7. Sanjit Joseph
8. Daniel Hernandez
9. Arthur Lu (remote)
## Meeting Details
- When: 11/29/2022 at 5:00PM
- Where: Design and Innovation Building
## Agenda:
- ### Old/Unresolved Business
- Resolve pretty print linting PR
- Resolve documentation not being merged to main PR
- ### New Business
- Create ADR for image storage
- Review sorting defaults (recent or top rated)
- Adding lists of reviewIDs corresponding to reviews which share specific star ratings
- Create function for retrieving top 20 reviews organized by decreasing star ratings
- Implement search for for flitering tags
- Frontend checked out new branch for alternate home page designs
- ### Next Meeting's Business
- Creation of team status video
## End Time
- 11/20/2022 at 3:00PM

View File

@ -0,0 +1,30 @@
# Meeting Minutes (11/30/2022)
## Team 29: Hackers1995
## Meeting Topic: Sprint 3 Continued
## Attendance
1. Rhea Bhutada
2. George Dubinin
4. Henry Feng
5. Kara Hoagland
7. Sanjit Joseph
9. Arthur Lu
## Meeting Details
- When: 11/30/2022 at 2:00PM
- Where: Design and Innovation Building
## Agenda:
- ### Old/Unresolved Business
- Fix empty page for no-tag search
- Catch testing up with what we implemented yesterday
- ### New Business
- Cache the site for local first (high priority)
- Implement editing form "in place" (optional for this sprint)
- Change icon for "add review" entry
- Overcoming UI test challenges
- ### Next Meeting's Business
- Creation of team status video
## End Time
- 11/30/2022 at 2:00PM

View File

@ -0,0 +1,22 @@
# Meeting Minutes (12/1/2022)
## Team 29: Hackers1995
## Meeting Topic: Weekly TA Catchup with Gagan
We are meeting with Gagan to discuss status video and general updates on project.
## Attendance
1. Rhea Bhutada
2. George Dubinin
3. Gagan Gopalaiah
## Meeting Details
- When: 12/1/2022 at 12:00PM
- Where: Zoom
## Discussion Points by Gagan
- Don't code up anything after Sunday. Reserve the time for bug fixes
- Final Interview is a 4-5 minute interview about general course specific topics
- Live demo of the app for Gagan
- Gagan asks us to evaluate instructional assistants
## End Time
- 12/1/2022 at 12:20PM

View File

@ -7,7 +7,7 @@
<title>Food Journal</title> <title>Food Journal</title>
<!--Add Favicon--> <!--Add Favicon-->
<link rel="icon" type="image/x-icon" href="./assets/images/icons/favicon.ico"> <link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico">
<!-- Review Card Custom Element --> <!-- Review Card Custom Element -->
<script src="assets/scripts/ReviewCard.js" type="module"></script> <script src="assets/scripts/ReviewCard.js" type="module"></script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -159,10 +159,10 @@ function initFormHandler() {
let tagLabel = document.createElement("label"); let tagLabel = document.createElement("label");
tagLabel.innerHTML = tagField.value; tagLabel.innerHTML = tagField.value;
tagLabel.setAttribute("class","tag"); tagLabel.setAttribute("class","tag");
tagSet.add(tagField.value.toLowerCase()); tagSet.add(tagSetVal);
tagLabel.addEventListener("click",()=> { tagLabel.addEventListener("click",()=> {
tagContainer.removeChild(tagLabel); tagContainer.removeChild(tagLabel);
tagSet.delete(tagField.value.toLowerCase()); tagSet.delete(tagSetVal);
}); });
tagContainer.append(tagLabel); tagContainer.append(tagLabel);

View File

@ -17,6 +17,7 @@ class ReviewCard extends HTMLElement {
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow-wrap: anywhere; overflow-wrap: anywhere;
cursor: pointer;
} }
a { a {
@ -149,7 +150,7 @@ class ReviewCard extends HTMLElement {
// Image setup // Image setup
let mealImg = document.createElement("img"); let mealImg = document.createElement("img");
mealImg.setAttribute("id", "a-mealImg"); mealImg.setAttribute("id", "a-meal-img");
mealImg.setAttribute("alt","Meal Photo Corrupted"); mealImg.setAttribute("alt","Meal Photo Corrupted");
mealImg.setAttribute("src",data["mealImg"]); mealImg.setAttribute("src",data["mealImg"]);
mealImg.addEventListener("error", function(e) { mealImg.addEventListener("error", function(e) {
@ -157,11 +158,11 @@ class ReviewCard extends HTMLElement {
e.onerror = null; e.onerror = null;
}); });
//meal name setup // Meal name setup
let meallabelDiv = document.createElement("div"); let meallabelDiv = document.createElement("div");
meallabelDiv.setAttribute("class", "meal-name-div"); meallabelDiv.setAttribute("class", "meal-name-div");
let mealLabel = document.createElement("label"); let mealLabel = document.createElement("label");
mealLabel.setAttribute("id", "a-mealName"); mealLabel.setAttribute("id", "a-meal-name");
mealLabel.setAttribute("class","meal-name"); mealLabel.setAttribute("class","meal-name");
mealLabel.innerHTML = data["mealName"]; mealLabel.innerHTML = data["mealName"];
meallabelDiv.append(mealLabel); meallabelDiv.append(mealLabel);
@ -188,7 +189,7 @@ class ReviewCard extends HTMLElement {
starsImg.setAttribute("num", data["rating"]); starsImg.setAttribute("num", data["rating"]);
ratingDiv.append(starsImg); ratingDiv.append(starsImg);
//added tags // Tags setup
let tagContainerDiv = document.createElement("div"); let tagContainerDiv = document.createElement("div");
tagContainerDiv.setAttribute("class", "tag-container-div"); tagContainerDiv.setAttribute("class", "tag-container-div");
let tagContainer = document.createElement("div"); let tagContainer = document.createElement("div");
@ -244,12 +245,12 @@ class ReviewCard extends HTMLElement {
// Getting the article elements for the review card // Getting the article elements for the review card
dataContainer["reviewID"] = this.reviewID; dataContainer["reviewID"] = this.reviewID;
// Get image //get image
let mealImg = this.shadowEl.getElementById("a-mealImg"); let mealImg = this.shadowEl.getElementById("a-meal-img");
dataContainer["mealImg"] = mealImg.getAttribute("src"); dataContainer["mealImg"] = mealImg.getAttribute("src");
// Get meal name //get meal name
let mealLabel = this.shadowEl.getElementById("a-mealName"); let mealLabel = this.shadowEl.getElementById("a-meal-name");
dataContainer["mealName"] = mealLabel.innerHTML; dataContainer["mealName"] = mealLabel.innerHTML;
// Get comment section // Get comment section

View File

@ -58,7 +58,7 @@ function setupInfo(){
} }
/** /**
* Sets up delete button to delete review from storage and switch to homepage * Sets up delete button to delete reveiw from storage and switch to homepage.
*/ */
function setupDelete(){ function setupDelete(){
let deleteBtn = document.getElementById("delete-btn"); let deleteBtn = document.getElementById("delete-btn");
@ -73,7 +73,7 @@ function setupDelete(){
} }
/** /**
* Sets up update button to reveal form and update info in storage and the current page * Sets up update button to reveal form and update info in storage and the current page.
*/ */
function setupUpdate(){ function setupUpdate(){
let updateBtn = document.getElementById("update-btn"); let updateBtn = document.getElementById("update-btn");
@ -102,8 +102,10 @@ function setupUpdate(){
while (tagContainer.firstChild) { while (tagContainer.firstChild) {
tagContainer.removeChild(tagContainer.firstChild); tagContainer.removeChild(tagContainer.firstChild);
} }
let tagSetVal;
for (let i = 0; i < currReview["tags"].length; i++) { for (let i = 0; i < currReview["tags"].length; i++) {
let tagSetVal = currReview["tags"][i].toLowerCase() tagSetVal = currReview["tags"][i].toLowerCase();
tagSet.add(tagSetVal); tagSet.add(tagSetVal);
let newTag = document.createElement("label"); let newTag = document.createElement("label");
newTag.setAttribute("class","tag"); newTag.setAttribute("class","tag");
@ -242,7 +244,7 @@ function setupUpdate(){
}); });
//adding tag to form functionality // Adding tag to form functionality
let tagAddBtn = document.getElementById("tag-add-btn"); let tagAddBtn = document.getElementById("tag-add-btn");
tagAddBtn.addEventListener("click", ()=> { tagAddBtn.addEventListener("click", ()=> {
let tagField = document.getElementById("tag-form"); let tagField = document.getElementById("tag-form");

View File

@ -40,13 +40,13 @@ export async function setReviewForm(page, review) {
*/ */
export async function checkCorrectness(root, prefix, expected){ export async function checkCorrectness(root, prefix, expected){
// Get the review image and check src // Get the review image and check src
let img = await root.$(`#${prefix}-mealImg`); let img = await root.$(`#${prefix}-meal-img`);
let imgSrc = await img.getProperty("src"); let imgSrc = await img.getProperty("src");
// Check src // Check src
assert.strictEqual(await imgSrc.jsonValue(), expected.imgSrc); assert.strictEqual(await imgSrc.jsonValue(), expected.imgSrc);
// Get the title, comment, and restaurant // Get the title, comment, and restaurant
let title = await root.$(`#${prefix}-mealName`); let title = await root.$(`#${prefix}-meal-name`);
let title_text = await title.getProperty("innerText"); let title_text = await title.getProperty("innerText");
let comment = await root.$(`#${prefix}-comments`); let comment = await root.$(`#${prefix}-comments`);
let comment_text = await comment.getProperty("innerText"); let comment_text = await comment.getProperty("innerText");

View File

@ -14,6 +14,14 @@ export function newReviewToStorage(review){
// adding to the tag keys // adding to the tag keys
addTagsToStorage(nextReviewId, review["tags"]); addTagsToStorage(nextReviewId, review["tags"]);
//adding to the star storage
let starArr = JSON.parse(localStorage.getItem(`star${review["rating"]}`));
if(!starArr){
starArr = [];
}
starArr.push(nextReviewId);
localStorage.setItem(`star${review["rating"]}`, JSON.stringify(starArr));
//updating our activeIDS list //updating our activeIDS list
let tempIdArr = JSON.parse(localStorage.getItem("activeIDS")); let tempIdArr = JSON.parse(localStorage.getItem("activeIDS"));
tempIdArr.push(nextReviewId); tempIdArr.push(nextReviewId);
@ -41,6 +49,70 @@ export function getReviewFromStorage(ID){
*/ */
export function updateReviewToStorage(ID, review){ export function updateReviewToStorage(ID, review){
let oldReview = JSON.parse(localStorage.getItem(`review${ID}`)); let oldReview = JSON.parse(localStorage.getItem(`review${ID}`));
let starArr = JSON.parse(localStorage.getItem(`star${review["rating"]}`));
//activeID update recency
let activeIDS = JSON.parse(localStorage.getItem("activeIDS"));
for (let i in activeIDS){
if(activeIDS[i] == ID){
activeIDS.splice(i,1);
activeIDS.push(ID);
break;
}
}
localStorage.setItem("activeIDS", JSON.stringify(activeIDS));
//star local storage update
if(oldReview["rating"] !== review["rating"]){
//first delete from previous rating array in storage
let oldStarArr = JSON.parse(localStorage.getItem(`star${oldReview["rating"]}`));
for (let i in oldStarArr) {
if (oldStarArr[i] == ID) {
//removing from corresponding rating array and updating local Storage
oldStarArr.splice(i,1);
break;
}
}
if(oldStarArr.length != 0){
localStorage.setItem(`star${oldReview["rating"]}`, JSON.stringify(oldStarArr));
} else {
localStorage.removeItem(`star${oldReview["rating"]}`);
}
//then add ID to array corresponding to new review rating
let newStarArr = starArr;
if(!newStarArr){
newStarArr = [];
}
newStarArr.push(ID);
localStorage.setItem(`star${review["rating"]}`, JSON.stringify(newStarArr));
} else if(starArr.length !== 1) {
//stars update recency if unchanged
for (let i in starArr){
if(starArr[i] == ID) {
starArr.splice(i,1);
starArr.push(ID);
break;
}
}
localStorage.setItem(`star${review["rating"]}`, JSON.stringify(starArr));
}
//specifically the unchanged tags update recency
let repeatedTags = review["tags"].filter(x => oldReview["tags"].includes(x));
let tagArr = [];
for (let i in repeatedTags){
tagArr = JSON.parse(localStorage.getItem(`!${repeatedTags[i]}`));
if(tagArr.length == 1){
for (let j in tagArr){
if(tagArr[j] == ID){
tagArr.splice(j,1);
tagArr.push(ID);
break;
}
}
localStorage.setItem(`!${repeatedTags[i]}`, JSON.stringify(tagArr));
}
}
//Get diff of tags and update storage //Get diff of tags and update storage
let deletedTags = oldReview["tags"].filter(x => !review["tags"].includes(x)); let deletedTags = oldReview["tags"].filter(x => !review["tags"].includes(x));
@ -57,12 +129,29 @@ export function updateReviewToStorage(ID, review){
* @param {string} ID of the review to delete * @param {string} ID of the review to delete
*/ */
export function deleteReviewFromStorage(ID){ export function deleteReviewFromStorage(ID){
//removing id number from activeIDS and star{rating}
let activeIDS = JSON.parse(localStorage.getItem("activeIDS")); let activeIDS = JSON.parse(localStorage.getItem("activeIDS"));
let reviewRating = JSON.parse(localStorage.getItem(`review${ID}`))["rating"];
let starArr = JSON.parse(localStorage.getItem(`star${reviewRating}`));
for (let i in starArr) {
if (starArr[i] == ID) {
//removing from corresponding rating array and updating local Storage
starArr.splice(i,1);
break;
}
}
if(starArr.length != 0){
localStorage.setItem(`star${reviewRating}`, JSON.stringify(starArr));
} else {
localStorage.removeItem(`star${reviewRating}`);
}
for (let i in activeIDS) { for (let i in activeIDS) {
if (activeIDS[i] == ID) { if (activeIDS[i] == ID) {
activeIDS.splice(i,1); activeIDS.splice(i,1);
localStorage.setItem("activeIDS", JSON.stringify(activeIDS)); localStorage.setItem("activeIDS", JSON.stringify(activeIDS));
let currReview = JSON.parse(localStorage.getItem(`review${ID}`)); let currReview = JSON.parse(localStorage.getItem(`review${ID}`));
deleteTagsFromStorage(ID, currReview["tags"]); deleteTagsFromStorage(ID, currReview["tags"]);
localStorage.removeItem(`review${ID}`); localStorage.removeItem(`review${ID}`);
@ -115,24 +204,9 @@ function addTagsToStorage(ID, addedTags) {
} }
/** /**
* Returns the top n reviews by ID. If there are less than n reviews, returns the most possible. * Test Helper Function to get all reviews from local storage
* @param {number} n number of reviews to return * @returns {Object} all active reviews from local storage
* @returns {Object} list of n reviews that are the top rated
*/ */
export function getTopReviewsFromStorage(n) {
}
/**
* Returns all reviews which contain the same tag specified.
* @param {string} tag to filter by
* @returns {Object} list of reviews that all contain the specified tag
*/
export function getReviewsByTag(tag) {
}
// legacy function
export function getAllReviewsFromStorage() { export function getAllReviewsFromStorage() {
if (!(localStorage.getItem("activeIDS"))) { if (!(localStorage.getItem("activeIDS"))) {
// we wanna init the active ID array and start the nextID count // we wanna init the active ID array and start the nextID count
@ -148,3 +222,46 @@ export function getAllReviewsFromStorage() {
} }
return reviews; return reviews;
} }
/**
* Get all IDs of active reviews (order: most recent)
* @returns {number[]} list of all active IDs by recency
*/
export function getIDsFromStorage() {
if (!(localStorage.getItem("activeIDS"))) {
// we wanna init the active ID array and start the nextID count
localStorage.setItem("activeIDS", JSON.stringify([]));
localStorage.setItem("nextID", JSON.stringify(0));
}
let activeIDS = JSON.parse(localStorage.getItem("activeIDS"));
return activeIDS.reverse();
}
/**
* Returns all review IDs which contain the same tag specified (order: most recent)
* @param {string} tag to filter by
* @returns {number[]} list of IDs of reviews that all contain the specified tag by recency
*/
export function getIDsByTag(tag) {
let tagArr = JSON.parse(localStorage.getItem("!" + tag.toLowerCase()));
if(!tagArr){
tagArr = [];
}
return tagArr.reverse();
}
/**
* Returns the top rated review IDs in order.
* @returns {number[]} list of IDs of reviews in order of top rating (most recent if equal rating)
*/
export function getTopIDsFromStorage() {
let resultArr = [];
for(let i = 5; i > 0; i--){
let starArr = JSON.parse(localStorage.getItem(`star${i}`));
if(!starArr){
continue;
}
resultArr = resultArr.concat(starArr.reverse());
}
return resultArr;
}

View File

@ -1,6 +1,6 @@
import {strict as assert} from "node:assert"; import {strict as assert} from "node:assert";
import {describe, it, before, after} from "mocha"; import {describe, it, before, after} from "mocha";
import {newReviewToStorage, getReviewFromStorage, updateReviewToStorage, deleteReviewFromStorage, getAllReviewsFromStorage, getTopReviewsFromStorage, getReviewsByTag} from "./localStorage.js"; import {newReviewToStorage, getReviewFromStorage, updateReviewToStorage, deleteReviewFromStorage, getAllReviewsFromStorage, getIDsByTag, getTopIDsFromStorage} from "./localStorage.js";
describe("test CRUD localStorage interaction", () => { describe("test CRUD localStorage interaction", () => {
@ -58,26 +58,27 @@ describe("test CRUD localStorage interaction", () => {
}).timeout(5000); }).timeout(5000);
it("test localStorage state during updating 1000 reviews", () => { it("test localStorage state during updating 1000 reviews", () => {
let reviews = getAllReviewsFromStorage();
let ids = JSON.parse(localStorage.getItem("activeIDS"));
for(let i = 0; i < 1000; i++){ for(let i = 0; i < 1000; i++){
let old_review = getReviewFromStorage(i);
let id = old_review.reviewID;
let new_review = { let new_review = {
"imgSrc": `updated sample src ${i}`, "imgSrc": `updated sample src ${id}`,
"mealName": `updated sample name ${i}`, "mealName": `updated sample name ${id}`,
"restaurant": `updated sample restaurant ${i}`, "restaurant": `updated sample restaurant ${id}`,
"rating": i*2+i, "reviewID": id,
"tags": [`tag ${3*i}`, `tag ${3*i + 1}`, `tag ${3*i + 2}`] "rating": (id % 5) + 1,
"tags": [`tag ${3*id}`, `tag ${3*id + 1}`, `tag ${3*id + 2}`]
}; };
new_review.reviewID = i;
reviews[i] = new_review; updateReviewToStorage(id, new_review);
updateReviewToStorage(i, new_review); let all_reviews = getAllReviewsFromStorage();
let active_ids = JSON.parse(localStorage.getItem("activeIDS"));
assert.deepEqual(getAllReviewsFromStorage(), reviews); assert.deepEqual(all_reviews[999], new_review);
assert.strictEqual(active_ids[999], id);
assert.deepEqual(getReviewFromStorage(i), new_review); assert.deepEqual(getReviewFromStorage(i), new_review);
assert.deepEqual(JSON.parse(localStorage.getItem("activeIDS")), ids);
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 1000); assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 1000);
} }
}).timeout(5000); }).timeout(5000);
@ -108,6 +109,7 @@ describe("test sort/filter localStorage interaction", () => {
before(() => { before(() => {
localStorage.clear(); localStorage.clear();
getAllReviewsFromStorage();
}); });
it("add sample data for sort and filter", () => { it("add sample data for sort and filter", () => {
@ -116,7 +118,7 @@ describe("test sort/filter localStorage interaction", () => {
"imgSrc": `sample src ${i}`, "imgSrc": `sample src ${i}`,
"mealName": `sample name ${i}`, "mealName": `sample name ${i}`,
"restaurant": `sample restaurant ${i}`, "restaurant": `sample restaurant ${i}`,
"rating": i, "rating": (i % 5) + 1,
"tags": [`tag ${i%3}`, `tag ${i < 50}`, "tag x"] "tags": [`tag ${i%3}`, `tag ${i < 50}`, "tag x"]
}; };
@ -124,134 +126,145 @@ describe("test sort/filter localStorage interaction", () => {
} }
}); });
it("test getTopReviewsFromStorage end behavior after create", () =>{ it("test getTopIDsFromStorage end behavior after create", () =>{
for(let i = 0; i <= 100; i++){ let top_reviews = getTopIDsFromStorage();
let top_reviews = getTopReviewsFromStorage(i); let prev = Infinity;
for(let j = 0; j < i; j++){ for(let i = 0; i < top_reviews.length; i++){
assert.strictEqual(top_reviews[j].rating, 99 - j); let review = getReviewFromStorage(top_reviews[i]);
assert.strictEqual(top_reviews[j].reviewID, 99 - j); assert.strictEqual(review.rating <= prev, true);
}
} }
}); });
it("test getReviewsByTag end behavior after create", () => { it("test getIDsByTag end behavior after create", () => {
let specific_tagged_reviews = []; let specific_tagged_reviews = [];
specific_tagged_reviews = getReviewsByTag("tag 0"); specific_tagged_reviews = getIDsByTag("tag 0");
assert.strictEqual(specific_tagged_reviews.length, 34); assert.strictEqual(specific_tagged_reviews.length, 34);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 0"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 3, 0); assert.strictEqual(review.tags.includes("tag 0"), true);
assert.strictEqual(review.reviewID % 3, 0);
} }
specific_tagged_reviews = getReviewsByTag("tag 1"); specific_tagged_reviews = getIDsByTag("tag 1");
assert.strictEqual(specific_tagged_reviews.length, 33); assert.strictEqual(specific_tagged_reviews.length, 33);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 1"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 3, 1); assert.strictEqual(review.tags.includes("tag 1"), true);
assert.strictEqual(review.reviewID % 3, 1);
} }
specific_tagged_reviews = getReviewsByTag("tag 2"); specific_tagged_reviews = getIDsByTag("tag 2");
assert.strictEqual(specific_tagged_reviews.length, 33); assert.strictEqual(specific_tagged_reviews.length, 33);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 2"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 3, 2); assert.strictEqual(review.tags.includes("tag 2"), true);
assert.strictEqual(review.reviewID % 3, 2);
} }
specific_tagged_reviews = getReviewsByTag("tag true"); specific_tagged_reviews = getIDsByTag("tag true");
assert.strictEqual(specific_tagged_reviews.length, 50); assert.strictEqual(specific_tagged_reviews.length, 50);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag true"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID < 50, true); assert.strictEqual(review.tags.includes("tag true"), true);
assert.strictEqual(review.reviewID < 50, true);
} }
specific_tagged_reviews = getReviewsByTag("tag false"); specific_tagged_reviews = getIDsByTag("tag false");
assert.strictEqual(specific_tagged_reviews.length, 50); assert.strictEqual(specific_tagged_reviews.length, 50);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag false"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID >= 50, true); assert.strictEqual(review.tags.includes("tag false"), true);
assert.strictEqual(review.reviewID >= 50, true);
} }
specific_tagged_reviews = getReviewsByTag("tag x"); specific_tagged_reviews = getIDsByTag("tag x");
assert.strictEqual(specific_tagged_reviews.length, 100); assert.strictEqual(specific_tagged_reviews.length, 100);
specific_tagged_reviews = getReviewsByTag("tag y"); specific_tagged_reviews = getIDsByTag("tag y");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
}); });
it("update sample data for sort and filter", () => { it("update sample data for sort and filter", () => {
for(let i = 0; i < 100; i++){ for(let i = 0; i < 100; i++){
let old_review = getReviewFromStorage(i);
let new_review = { let new_review = {
"imgSrc": `sample src ${i}`, "imgSrc": `sample src ${i}`,
"mealName": `sample name ${i}`, "mealName": `sample name ${i}`,
"restaurant": `sample restaurant ${i}`, "restaurant": `sample restaurant ${i}`,
"rating": 99-i, "reviewID": old_review.reviewID,
"tags": [`tag ${i%4}`, `tag ${i < 37}`, "tag y"] "rating": (i % 5) + 1,
"tags": [`tag ${i % 4}`, `tag ${i < 37}`, "tag y"]
}; };
updateReviewToStorage(i, new_review); updateReviewToStorage(old_review.reviewID, new_review);
} }
}); });
it("test getTopReviewsFromStorage end behavior after create", () =>{ it("test getTopIDsFromStorage end behavior after create", () =>{
for(let i = 0; i <= 100; i++){ let top_reviews = getTopIDsFromStorage();
let top_reviews = getTopReviewsFromStorage(i); let prev = Infinity;
for(let j = 0; j < i; j++){ for(let i = 0; i < top_reviews.length; i++){
assert.strictEqual(top_reviews[j].rating, 99 - j); let review = getReviewFromStorage(top_reviews[i]);
assert.strictEqual(top_reviews[j].reviewID, j); assert.strictEqual(review.rating <= prev, true);
}
} }
}); });
it("test getReviewsByTag end behavior after update", () => { it("test getIDsByTag end behavior after update", () => {
let specific_tagged_reviews = []; let specific_tagged_reviews = [];
specific_tagged_reviews = getReviewsByTag("tag 0"); specific_tagged_reviews = getIDsByTag("tag 0");
assert.strictEqual(specific_tagged_reviews.length, 25); assert.strictEqual(specific_tagged_reviews.length, 25);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 0"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 0); assert.strictEqual(review.tags.includes("tag 0"), true);
assert.strictEqual(review.reviewID % 4, 0);
} }
specific_tagged_reviews = getReviewsByTag("tag 1"); specific_tagged_reviews = getIDsByTag("tag 1");
assert.strictEqual(specific_tagged_reviews.length, 25); assert.strictEqual(specific_tagged_reviews.length, 25);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 1"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 1); assert.strictEqual(review.tags.includes("tag 1"), true);
assert.strictEqual(review.reviewID % 4, 1);
} }
specific_tagged_reviews = getReviewsByTag("tag 2"); specific_tagged_reviews = getIDsByTag("tag 2");
assert.strictEqual(specific_tagged_reviews.length, 25); assert.strictEqual(specific_tagged_reviews.length, 25);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 2"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 2); assert.strictEqual(review.tags.includes("tag 2"), true);
assert.strictEqual(review.reviewID % 4, 2);
} }
specific_tagged_reviews = getReviewsByTag("tag 3"); specific_tagged_reviews = getIDsByTag("tag 3");
assert.strictEqual(specific_tagged_reviews.length, 25); assert.strictEqual(specific_tagged_reviews.length, 25);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 3"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 3); assert.strictEqual(review.tags.includes("tag 3"), true);
assert.strictEqual(review.reviewID % 4, 3);
} }
specific_tagged_reviews = getReviewsByTag("tag true"); specific_tagged_reviews = getIDsByTag("tag true");
assert.strictEqual(specific_tagged_reviews.length, 37); assert.strictEqual(specific_tagged_reviews.length, 37);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag true"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID < 37, true); assert.strictEqual(review.tags.includes("tag true"), true);
assert.strictEqual(review.reviewID < 37, true);
} }
specific_tagged_reviews = getReviewsByTag("tag false"); specific_tagged_reviews = getIDsByTag("tag false");
assert.strictEqual(specific_tagged_reviews.length, 63); assert.strictEqual(specific_tagged_reviews.length, 63);
for(let i = 0; i < specific_tagged_reviews.length; i++){ for(let i = 0; i < specific_tagged_reviews.length; i++){
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag false"), true); let review = getReviewFromStorage(specific_tagged_reviews[i]);
assert.strictEqual(specific_tagged_reviews[i].reviewID >= 37, true); assert.strictEqual(review.tags.includes("tag false"), true);
assert.strictEqual(review.reviewID >= 37, true);
} }
specific_tagged_reviews = getReviewsByTag("tag x"); specific_tagged_reviews = getIDsByTag("tag x");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag y"); specific_tagged_reviews = getIDsByTag("tag y");
assert.strictEqual(specific_tagged_reviews.length, 100); assert.strictEqual(specific_tagged_reviews.length, 100);
}); });
@ -261,38 +274,38 @@ describe("test sort/filter localStorage interaction", () => {
} }
}); });
it("test getTopReviewsFromStorage end behavior after delete", () =>{ it("test getTopIDsFromStorage end behavior after delete", () =>{
for(let i = 0; i <= 100; i++){ for(let i = 0; i <= 100; i++){
let top_reviews = getTopReviewsFromStorage(i); let top_reviews = getTopIDsFromStorage(i);
assert.deepEqual(top_reviews, []); assert.deepEqual(top_reviews, []);
} }
}); });
it("test getReviewsByTag end behavior after delete", () => { it("test getIDsByTag end behavior after delete", () => {
let specific_tagged_reviews = []; let specific_tagged_reviews = [];
specific_tagged_reviews = getReviewsByTag("tag 0"); specific_tagged_reviews = getIDsByTag("tag 0");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag 1"); specific_tagged_reviews = getIDsByTag("tag 1");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag 2"); specific_tagged_reviews = getIDsByTag("tag 2");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag 3"); specific_tagged_reviews = getIDsByTag("tag 3");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag true"); specific_tagged_reviews = getIDsByTag("tag true");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag false"); specific_tagged_reviews = getIDsByTag("tag false");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag x"); specific_tagged_reviews = getIDsByTag("tag x");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
specific_tagged_reviews = getReviewsByTag("tag y"); specific_tagged_reviews = getIDsByTag("tag y");
assert.deepEqual(specific_tagged_reviews, []); assert.deepEqual(specific_tagged_reviews, []);
}); });

View File

@ -1,15 +1,13 @@
// main.js // main.js
import {getAllReviewsFromStorage} from "./localStorage.js"; import {getIDsByTag, getIDsFromStorage, getReviewFromStorage, getTopIDsFromStorage} from "./localStorage.js";
// Run the init() function when the page has loaded // Run the init() function when the page has loaded
window.addEventListener("DOMContentLoaded", init); window.addEventListener("DOMContentLoaded", init);
function init() { function init() {
// Get the reviews from localStorage //initial population of review container
let reviews = getAllReviewsFromStorage(); sortAndFilter(false, null);
// Add each reviews to the <main> element //Add the event listeners to dropdown and search bar
addReviewsToDocument(reviews);
// Add the event listeners to the form elements
initFormHandler(); initFormHandler();
} }
@ -22,21 +20,141 @@ function addReviewsToDocument(reviews) {
reviews.forEach(review => { reviews.forEach(review => {
let newReview = document.createElement("review-card"); let newReview = document.createElement("review-card");
newReview.data = review; newReview.data = review;
//TODO: want to append it to whatever the box is in layout
reviewBox.append(newReview); reviewBox.append(newReview);
}); });
} }
/** /**
* Adds the necessary event handlers to <form> and the clear storage * Adds the necessary event handlers to search-btn and sort
* <button>.
*/ */
function initFormHandler() { function initFormHandler() {
//btn to create form (could be its own function?) //grabbing search field
let createBtn = document.getElementById("create-btn"); let searchField = document.getElementById("search-bar");
createBtn.addEventListener("click", function(){ let searchBtn = document.getElementById("search-btn");
window.location.assign("./CreatePage.html"); let searchTag = null;
//adding search functionality
//TODO: Add ability to enter without refresh of search bar
//filter by selected tag when button clicked
searchBtn.addEventListener("click", function(){
searchTag = searchField.value;
sortAndFilter(searchTag);
}); });
//for clearing tag filter
let clearSearchBtn = document.getElementById("clear-search");
clearSearchBtn.addEventListener("click", function(){
searchTag = null;
searchField.value = "";
sortAndFilter(searchTag);
});
//sort by selected method
let sortMethod = document.getElementById("sort");
sortMethod.addEventListener("input", function(){
sortAndFilter(searchTag);
});
} }
/**
* Deciphers sort and filter to populate the review-container
* @param {string} searchTag tag name to filter by
*/
function sortAndFilter(searchTag){
let reviewBox = document.getElementById("review-container");
let sortMethod = document.getElementById("sort");
//clear review container
while(reviewBox.firstChild){
reviewBox.removeChild(reviewBox.firstChild);
}
let reviewIDs = [];
//sort method: most recent
if(sortMethod.value == "recent"){
//tag filtered most recent
if(searchTag){
reviewIDs = getIDsByTag(searchTag);
}
//most recent
else {
reviewIDs = getIDsFromStorage();
}
//reversed for recency
loadReviews(0, reviewIDs);
}
//sort method: top rated
else if (sortMethod.value == "top"){
//tag filtered top rated
if(searchTag){
//intersection of top ids list and ids by tag in top ids order
reviewIDs = getTopIDsFromStorage().filter(x => getIDsByTag(searchTag).includes(x));
}
//top rated
else {
reviewIDs = getTopIDsFromStorage();
}
loadReviews(0, reviewIDs);
}
}
/**
* Populate review-container with 9 more reviews
* @param {number} index review index to begin with
* @param {number[]} reviewIDs ordered array of reviews
*/
function loadReviews(index, reviewIDs){
let reviewBox = document.getElementById("review-container");
// label if there are no reviews to display
if(reviewIDs.length == 0){
let emptyLabel = document.createElement("label");
emptyLabel.setAttribute("id", "empty");
emptyLabel.innerText = "No Reviews To Display";
reviewBox.append(emptyLabel);
} else {
let emptyLabel = document.getElementById("empty");
if(emptyLabel){
reviewBox.removeChild(emptyLabel);
}
}
let moreBtn = document.getElementById("more-btn");
//delete load more button if exists
if(moreBtn){
reviewBox.removeChild(moreBtn);
}
let reviewArr = [];
//check if there are more than 9 reviews left
if(index + 9 > reviewIDs.length - 1){
//add remaining reviews to review container
for(let i = index; i < reviewIDs.length; i++){
reviewArr.push(getReviewFromStorage(reviewIDs[i]));
}
addReviewsToDocument(reviewArr);
} else {
//add 9 more reviews to container
for(let i = index; i < index + 9; i++){
reviewArr.push(getReviewFromStorage(reviewIDs[i]));
}
addReviewsToDocument(reviewArr);
//create and add load more button
moreBtn = document.createElement("button");
moreBtn.setAttribute("id", "more-btn");
moreBtn.innerText = "Load More";
//if load more clicked, load 9 more
moreBtn.addEventListener("click", function(){loadReviews(index + 9, reviewIDs);});
reviewBox.append(moreBtn);
}
}
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
await navigator.serviceWorker.register("./sw.js", {scope: "./"});
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
registerServiceWorker();

View File

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -9,39 +9,48 @@
<!--Add Favicon--> <!--Add Favicon-->
<link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico"> <link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico">
<!-- Review Card Custom Element --> <!-- Recipe Card Custom Element -->
<script src="assets/scripts/ReviewCard.js" type="module"></script> <script src="assets/scripts/ReviewCard.js" type="module"></script>
<!-- Homepage Stylesheets & Scripts --> <!-- Main Stylesheets & Scripts -->
<!-- Temporarily commented out reset.css due to furthur discussion needed on the values of the default config-->
<!-- <link rel="stylesheet" href="/static/reset.css" /> -->
<link rel="stylesheet" href="./static/homepage.css" /> <link rel="stylesheet" href="./static/homepage.css" />
<script src="assets/scripts/main.js" type="module"></script> <script src="assets/scripts/main.js" type="module"></script>
</head> </head>
<body>
<body>
<header> <header>
<div class="top-bar"> <div class="top-bar">
<img src ="./assets/images/Logo.png" alt="logo" /> <img src="./assets/images/Logo.png" alt="logo" />
<h1> Food Journal </h1> <h1> Food Journal </h1>
<img src ="./assets/images/Logo.png" alt="logo" /> <img src="./assets/images/Logo.png" alt="logo" />
</div> </div>
</header> </header>
<main> <main>
<div class="body-container"> <div class="body-container">
<div style="width: 20%;"></div> <div style="width: 20%;"></div>
<div style="width: 60%;"> <div style="width: 60%;">
<div class="search-bar"> <div class="search-bar">
<form id="form"> <form id="form">
<input type="search" id="searching" name="searchBar" placeholder="Search journal..."> <label for="sort">Sorting Method:</label>
<button class="click" type="search"> Search </button> <select id="sort">
<div class="Filter-box"> <option value="recent">Most Recent</option>
<option value="top">Top Rated</option>
</div> </select>
<input type="search" id="search-bar" name="searchBar" placeholder="Search tags...">
<button id="clear-search">Clear Search</button>
</form> </form>
<img src="./assets/images/search_button.png" alt="SEARCH BTN" id="search-btn" />
</div> </div>
<div class="center-display">
<img src ="./assets/images/Grouppink.png" alt="CREATE" style="opacity: 100%;" id="create-btn" title="Add an entry!" onclick="window.location.assign('./CreatePage.html')"/>
<h2 id="recent-reviews-text"> Recent Reviews </h2>
<img src ="./assets/images/Grouppink.png" style="opacity:0;"/>
<div class="center-display">
<img src="./assets/images/create_button.png" alt="CREATE" id="create-btn" title="Add an entry!"
onclick="window.location.assign('./CreatePage.html')" />
<h2 id="recent-reviews-text"> Recent Reviews </h2>
<img src="./assets/images/create_button.png" id="create-btn-invis" draggable="false" />
</div> </div>
<div class="review-container" id="review-container"></div> <div class="review-container" id="review-container"></div>
@ -50,5 +59,6 @@
</div> </div>
</div> </div>
</main> </main>
</body> </body>
</html> </html>

View File

@ -17,6 +17,7 @@
border: 2px solid rgb(31 41 32); border: 2px solid rgb(31 41 32);
border-radius: 8px; border-radius: 8px;
background-color: #f7dfd5; background-color: #f7dfd5;
word-break: break-all;
} }
#d-meal-img { #d-meal-img {

View File

@ -52,18 +52,34 @@ body {
.search-bar { .search-bar {
display: flex; display: flex;
justify-content: center; justify-content: center;
font-family: "Century Gothic", sans-serif;
font-weight: bold;
color: #516754;
}
#sort {
margin-right: 1em;
} }
.search-bar > form { .search-bar > form {
float: right; float: right;
padding: 6px 10px; padding: 6px 10px;
/*
margin-top: 8px; margin-top: 8px;
margin-right: 16px; margin-right: 16px;
*/
background: rgb(239 183 183); background: rgb(239 183 183);
font-size: 17px; font-size: 17px;
border: none; border: none;
border-radius: 12px; border-radius: 12px;
cursor: pointer; }
#search-btn {
position: relative;
align-self: center;
width: 30px;
height: 30px;
} }
#recent-reviews-text { #recent-reviews-text {
@ -78,6 +94,17 @@ img#create-btn {
align-self: center; align-self: center;
padding-left: 2.5%; padding-left: 2.5%;
padding-right: 2.5%; padding-right: 2.5%;
cursor: pointer;
width: 10%;
height: 10%;
}
img#create-btn-invis {
opacity: 0;
padding-left: 2.5%;
padding-right: 2.5%;
width: 10%;
height: 10%;
} }
.review-container { .review-container {

63
source/sw.js Normal file
View File

@ -0,0 +1,63 @@
const CACHE_NAME = "food-journal-v1";
const ASSETS = [
"index.html",
"ReviewDetails.html",
"CreatePage.html",
"static/CoveredByYourGrace-Regular.ttf",
"static/CreatePage.css",
"static/Form.css",
"static/homepage.css",
"static/ReviewDetails.css",
"assets/images/0-star.svg",
"assets/images/1-star.svg",
"assets/images/2-star.svg",
"assets/images/3-star.svg",
"assets/images/4-star.svg",
"assets/images/5-star.svg",
"assets/images/create_button.png",
"assets/images/default_plate.png",
"assets/images/delete_icon_for_interface.png",
"assets/images/edit_button_for_interface.png",
"assets/images/home_button_for_interface.png",
"assets/images/favicon.ico",
"assets/images/Logo.png",
"assets/images/search_button.png",
"assets/scripts/CreatePage.js",
"assets/scripts/localStorage.js",
"assets/scripts/main.js",
"assets/scripts/ReviewCard.js",
"assets/scripts/ReviewDetails.js"
];
/**
* Adds the install listener where the app assets are added to the cache
*/
self.addEventListener("install", async () => {
// open the cace
const cache = await caches.open(CACHE_NAME);
// add all elements in ASSETS to the cache, these are all the files requried for the app to run
await cache.addAll(ASSETS);
});
/**
* Adds an event listener on fetch events to serve cached resources while offline
* Uses a network first structure to prioritize fetching from network in case of app updates.
* If there are important updates, we want the user to get those if possible.
*/
self.addEventListener("fetch", (event) => {
// add a response to the fetch event
event.respondWith(caches.open(CACHE_NAME).then((cache) => {
// try to return a network fetch response
return fetch(event.request).then((fetchedResponse) => {
// if there is a response, add it to the cache
cache.put(event.request, fetchedResponse.clone());
// return the network response
return fetchedResponse;
}).catch(() => {
// If there is not a network response, return the cached response
// The ignoreVary option is used here to fix an issue where the service worker
// would not serve certain requests unless the page was refreshed at least once
return cache.match(event.request, {ignoreVary: true, ignoreSearch: true});
});
}));
});

View File

@ -0,0 +1,16 @@
# Backend Storage Structure
- Status: Accept
- Deciders: Rhea Bhutada, Kara Hoagland, Gavyn Ezell, George Dubinin, Henry Feng
- Date: 11/29/2022
## Decision Drivers
- Needed more efficient way of storing reviews that are created, for more efficient testing, updating, accessing, and deleting.
## Considered Options
- localStorage
## Decision Outcome
Using local storage to maintain the "local first" requirement.
Moved away from array of objects for storing reviews, reviews are stored individually as keys in localStorage, under the "review{id}" format. Each key
corresponds to object containing review data. We also have an array stored in local storage, named "activeIDs" which keeps track of id numbers that are attached
to created reviews.

View File

@ -0,0 +1,15 @@
# Opening Specific Reviews
- Status: Accept
- Deciders: Rhea Bhutada, Kara Hoagland, Gavyn Ezell, George Dubinin, Henry Feng
- Date: 11/29/2022
## Decision Drivers
- When opening up a review, browser needs to know what review ID to use for loading the review page data
## Considered Options
- sessionStorage
## Decision Outcome
Review cards have event listeners that will add their associated review ID number to session storage so
when the review loads, the browser will use the id stored to pull exact data corresponding to the review.

View File

@ -0,0 +1,13 @@
# Organizing Review Under Tags
- Status: Accept
- Deciders: Rhea Bhutada, Kara Hoagland, Gavyn Ezell, George Dubinin, Henry Feng
- Date: 11/29/2022
## Decision Drivers
- Needed to keep track of reviews under certain given tags for filtering feature.
## Considered Options
- localStorage
## Decision Outcome
For every tag create a key under that tag name in localStorage. They will store an array of IDs that correspond to reviews that contain that tag.

View File

@ -0,0 +1,19 @@
# Use a network first cache second for service worker architecture
- Status: in consideration
- Deciders: Arthur Lu, Kara Hoagland, Rhea Bhutada, George Dubinin
- Date: 12 / 01 / 22
## Decision Drivers
- Need to balance the need for user ease of use and local first priority
- Users should expect to update their app easily when they have network, but may not be expected to know how to perform a hard refresh
- Local first priority means we should avoid unnecessary network activity when possible
## Considered Options
- Network first cache second
- Cache first network second
## Decision Outcome
Chosen Option: Network first for automatic app updating.