mirror of
https://github.com/cse110-fa22-group29/cse110-fa22-group29.git
synced 2024-12-25 16:39:09 +00:00
Merge branch 'sprint-3' into 49-image-feature
This commit is contained in:
commit
fa32a61df9
29
.github/workflows/prettier.yml
vendored
29
.github/workflows/prettier.yml
vendored
@ -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
|
BIN
admin/cipipeline/phase2.drawio.png
Normal file
BIN
admin/cipipeline/phase2.drawio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
47
admin/cipipeline/phase2.md
Normal file
47
admin/cipipeline/phase2.md
Normal 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
BIN
admin/cipipeline/phase2.mp4
Normal file
Binary file not shown.
35
admin/meetings/112922-Sprint3Opener.md
Normal file
35
admin/meetings/112922-Sprint3Opener.md
Normal 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
|
30
admin/meetings/113022-Sprint3Cont.md
Normal file
30
admin/meetings/113022-Sprint3Cont.md
Normal 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
|
22
admin/meetings/1212022-check10.md
Normal file
22
admin/meetings/1212022-check10.md
Normal 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
|
@ -24,4 +24,4 @@
|
||||
"stylelint": "14.14.1",
|
||||
"stylelint-config-standard": "^29.0.0"
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
<title>Food Journal</title>
|
||||
|
||||
<!--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 -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.0 KiB |
BIN
source/assets/images/create_button.png
Normal file
BIN
source/assets/images/create_button.png
Normal file
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 |
BIN
source/assets/images/search_button.png
Normal file
BIN
source/assets/images/search_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@ -159,10 +159,10 @@ function initFormHandler() {
|
||||
let tagLabel = document.createElement("label");
|
||||
tagLabel.innerHTML = tagField.value;
|
||||
tagLabel.setAttribute("class","tag");
|
||||
tagSet.add(tagField.value.toLowerCase());
|
||||
tagSet.add(tagSetVal);
|
||||
tagLabel.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(tagLabel);
|
||||
tagSet.delete(tagField.value.toLowerCase());
|
||||
tagSet.delete(tagSetVal);
|
||||
});
|
||||
|
||||
tagContainer.append(tagLabel);
|
||||
|
@ -17,6 +17,7 @@ class ReviewCard extends HTMLElement {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -149,7 +150,7 @@ class ReviewCard extends HTMLElement {
|
||||
|
||||
// Image setup
|
||||
let mealImg = document.createElement("img");
|
||||
mealImg.setAttribute("id", "a-mealImg");
|
||||
mealImg.setAttribute("id", "a-meal-img");
|
||||
mealImg.setAttribute("alt","Meal Photo Corrupted");
|
||||
mealImg.setAttribute("src",data["mealImg"]);
|
||||
mealImg.addEventListener("error", function(e) {
|
||||
@ -157,11 +158,11 @@ class ReviewCard extends HTMLElement {
|
||||
e.onerror = null;
|
||||
});
|
||||
|
||||
//meal name setup
|
||||
// Meal name setup
|
||||
let meallabelDiv = document.createElement("div");
|
||||
meallabelDiv.setAttribute("class", "meal-name-div");
|
||||
let mealLabel = document.createElement("label");
|
||||
mealLabel.setAttribute("id", "a-mealName");
|
||||
mealLabel.setAttribute("id", "a-meal-name");
|
||||
mealLabel.setAttribute("class","meal-name");
|
||||
mealLabel.innerHTML = data["mealName"];
|
||||
meallabelDiv.append(mealLabel);
|
||||
@ -188,7 +189,7 @@ class ReviewCard extends HTMLElement {
|
||||
starsImg.setAttribute("num", data["rating"]);
|
||||
ratingDiv.append(starsImg);
|
||||
|
||||
//added tags
|
||||
// Tags setup
|
||||
let tagContainerDiv = document.createElement("div");
|
||||
tagContainerDiv.setAttribute("class", "tag-container-div");
|
||||
let tagContainer = document.createElement("div");
|
||||
@ -244,12 +245,12 @@ class ReviewCard extends HTMLElement {
|
||||
// Getting the article elements for the review card
|
||||
dataContainer["reviewID"] = this.reviewID;
|
||||
|
||||
// Get image
|
||||
let mealImg = this.shadowEl.getElementById("a-mealImg");
|
||||
//get image
|
||||
let mealImg = this.shadowEl.getElementById("a-meal-img");
|
||||
dataContainer["mealImg"] = mealImg.getAttribute("src");
|
||||
|
||||
// Get meal name
|
||||
let mealLabel = this.shadowEl.getElementById("a-mealName");
|
||||
//get meal name
|
||||
let mealLabel = this.shadowEl.getElementById("a-meal-name");
|
||||
dataContainer["mealName"] = mealLabel.innerHTML;
|
||||
|
||||
// Get comment section
|
||||
|
@ -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(){
|
||||
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(){
|
||||
let updateBtn = document.getElementById("update-btn");
|
||||
@ -102,8 +102,10 @@ function setupUpdate(){
|
||||
while (tagContainer.firstChild) {
|
||||
tagContainer.removeChild(tagContainer.firstChild);
|
||||
}
|
||||
|
||||
let tagSetVal;
|
||||
for (let i = 0; i < currReview["tags"].length; i++) {
|
||||
let tagSetVal = currReview["tags"][i].toLowerCase()
|
||||
tagSetVal = currReview["tags"][i].toLowerCase();
|
||||
tagSet.add(tagSetVal);
|
||||
let newTag = document.createElement("label");
|
||||
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");
|
||||
tagAddBtn.addEventListener("click", ()=> {
|
||||
let tagField = document.getElementById("tag-form");
|
||||
|
@ -40,13 +40,13 @@ export async function setReviewForm(page, review) {
|
||||
*/
|
||||
export async function checkCorrectness(root, prefix, expected){
|
||||
// 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");
|
||||
// Check src
|
||||
assert.strictEqual(await imgSrc.jsonValue(), expected.imgSrc);
|
||||
|
||||
// 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 comment = await root.$(`#${prefix}-comments`);
|
||||
let comment_text = await comment.getProperty("innerText");
|
||||
|
@ -13,6 +13,14 @@ export function newReviewToStorage(review){
|
||||
|
||||
// adding to the tag keys
|
||||
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
|
||||
let tempIdArr = JSON.parse(localStorage.getItem("activeIDS"));
|
||||
@ -41,6 +49,70 @@ export function getReviewFromStorage(ID){
|
||||
*/
|
||||
export function updateReviewToStorage(ID, review){
|
||||
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
|
||||
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
|
||||
*/
|
||||
export function deleteReviewFromStorage(ID){
|
||||
//removing id number from activeIDS and star{rating}
|
||||
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) {
|
||||
if (activeIDS[i] == ID) {
|
||||
activeIDS.splice(i,1);
|
||||
localStorage.setItem("activeIDS", JSON.stringify(activeIDS));
|
||||
|
||||
let currReview = JSON.parse(localStorage.getItem(`review${ID}`));
|
||||
deleteTagsFromStorage(ID, currReview["tags"]);
|
||||
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.
|
||||
* @param {number} n number of reviews to return
|
||||
* @returns {Object} list of n reviews that are the top rated
|
||||
* Test Helper Function to get all reviews from local storage
|
||||
* @returns {Object} all active reviews from local storage
|
||||
*/
|
||||
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() {
|
||||
if (!(localStorage.getItem("activeIDS"))) {
|
||||
// we wanna init the active ID array and start the nextID count
|
||||
@ -147,4 +221,47 @@ export function getAllReviewsFromStorage() {
|
||||
reviews.push(currReview);
|
||||
}
|
||||
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;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import {strict as assert} from "node:assert";
|
||||
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", () => {
|
||||
|
||||
@ -58,26 +58,27 @@ describe("test CRUD localStorage interaction", () => {
|
||||
}).timeout(5000);
|
||||
|
||||
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++){
|
||||
let old_review = getReviewFromStorage(i);
|
||||
let id = old_review.reviewID;
|
||||
|
||||
let new_review = {
|
||||
"imgSrc": `updated sample src ${i}`,
|
||||
"mealName": `updated sample name ${i}`,
|
||||
"restaurant": `updated sample restaurant ${i}`,
|
||||
"rating": i*2+i,
|
||||
"tags": [`tag ${3*i}`, `tag ${3*i + 1}`, `tag ${3*i + 2}`]
|
||||
"imgSrc": `updated sample src ${id}`,
|
||||
"mealName": `updated sample name ${id}`,
|
||||
"restaurant": `updated sample restaurant ${id}`,
|
||||
"reviewID": id,
|
||||
"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(JSON.parse(localStorage.getItem("activeIDS")), ids);
|
||||
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 1000);
|
||||
}
|
||||
}).timeout(5000);
|
||||
@ -108,6 +109,7 @@ describe("test sort/filter localStorage interaction", () => {
|
||||
|
||||
before(() => {
|
||||
localStorage.clear();
|
||||
getAllReviewsFromStorage();
|
||||
});
|
||||
|
||||
it("add sample data for sort and filter", () => {
|
||||
@ -116,7 +118,7 @@ describe("test sort/filter localStorage interaction", () => {
|
||||
"imgSrc": `sample src ${i}`,
|
||||
"mealName": `sample name ${i}`,
|
||||
"restaurant": `sample restaurant ${i}`,
|
||||
"rating": i,
|
||||
"rating": (i % 5) + 1,
|
||||
"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", () =>{
|
||||
for(let i = 0; i <= 100; i++){
|
||||
let top_reviews = getTopReviewsFromStorage(i);
|
||||
for(let j = 0; j < i; j++){
|
||||
assert.strictEqual(top_reviews[j].rating, 99 - j);
|
||||
assert.strictEqual(top_reviews[j].reviewID, 99 - j);
|
||||
}
|
||||
it("test getTopIDsFromStorage end behavior after create", () =>{
|
||||
let top_reviews = getTopIDsFromStorage();
|
||||
let prev = Infinity;
|
||||
for(let i = 0; i < top_reviews.length; i++){
|
||||
let review = getReviewFromStorage(top_reviews[i]);
|
||||
assert.strictEqual(review.rating <= prev, true);
|
||||
}
|
||||
});
|
||||
|
||||
it("test getReviewsByTag end behavior after create", () => {
|
||||
it("test getIDsByTag end behavior after create", () => {
|
||||
let specific_tagged_reviews = [];
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag 0");
|
||||
specific_tagged_reviews = getIDsByTag("tag 0");
|
||||
assert.strictEqual(specific_tagged_reviews.length, 34);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 0"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 3, 0);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 1"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 3, 1);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 2"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 3, 2);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag true"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID < 50, true);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag false"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID >= 50, true);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag y");
|
||||
specific_tagged_reviews = getIDsByTag("tag y");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
});
|
||||
|
||||
it("update sample data for sort and filter", () => {
|
||||
for(let i = 0; i < 100; i++){
|
||||
let old_review = getReviewFromStorage(i);
|
||||
let new_review = {
|
||||
"imgSrc": `sample src ${i}`,
|
||||
"mealName": `sample name ${i}`,
|
||||
"restaurant": `sample restaurant ${i}`,
|
||||
"rating": 99-i,
|
||||
"tags": [`tag ${i%4}`, `tag ${i < 37}`, "tag y"]
|
||||
"reviewID": old_review.reviewID,
|
||||
"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", () =>{
|
||||
for(let i = 0; i <= 100; i++){
|
||||
let top_reviews = getTopReviewsFromStorage(i);
|
||||
for(let j = 0; j < i; j++){
|
||||
assert.strictEqual(top_reviews[j].rating, 99 - j);
|
||||
assert.strictEqual(top_reviews[j].reviewID, j);
|
||||
}
|
||||
it("test getTopIDsFromStorage end behavior after create", () =>{
|
||||
let top_reviews = getTopIDsFromStorage();
|
||||
let prev = Infinity;
|
||||
for(let i = 0; i < top_reviews.length; i++){
|
||||
let review = getReviewFromStorage(top_reviews[i]);
|
||||
assert.strictEqual(review.rating <= prev, true);
|
||||
}
|
||||
});
|
||||
|
||||
it("test getReviewsByTag end behavior after update", () => {
|
||||
it("test getIDsByTag end behavior after update", () => {
|
||||
let specific_tagged_reviews = [];
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag 0");
|
||||
specific_tagged_reviews = getIDsByTag("tag 0");
|
||||
assert.strictEqual(specific_tagged_reviews.length, 25);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 0"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 0);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 1"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 1);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 2"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 2);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag 3"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID % 4, 3);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag true"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID < 37, true);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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);
|
||||
for(let i = 0; i < specific_tagged_reviews.length; i++){
|
||||
assert.strictEqual(specific_tagged_reviews[i].tags.includes("tag false"), true);
|
||||
assert.strictEqual(specific_tagged_reviews[i].reviewID >= 37, true);
|
||||
let review = getReviewFromStorage(specific_tagged_reviews[i]);
|
||||
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, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag y");
|
||||
specific_tagged_reviews = getIDsByTag("tag y");
|
||||
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++){
|
||||
let top_reviews = getTopReviewsFromStorage(i);
|
||||
let top_reviews = getTopIDsFromStorage(i);
|
||||
assert.deepEqual(top_reviews, []);
|
||||
}
|
||||
});
|
||||
|
||||
it("test getReviewsByTag end behavior after delete", () => {
|
||||
it("test getIDsByTag end behavior after delete", () => {
|
||||
let specific_tagged_reviews = [];
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag 0");
|
||||
specific_tagged_reviews = getIDsByTag("tag 0");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag 1");
|
||||
specific_tagged_reviews = getIDsByTag("tag 1");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag 2");
|
||||
specific_tagged_reviews = getIDsByTag("tag 2");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag 3");
|
||||
specific_tagged_reviews = getIDsByTag("tag 3");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag true");
|
||||
specific_tagged_reviews = getIDsByTag("tag true");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag false");
|
||||
specific_tagged_reviews = getIDsByTag("tag false");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag x");
|
||||
specific_tagged_reviews = getIDsByTag("tag x");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
|
||||
specific_tagged_reviews = getReviewsByTag("tag y");
|
||||
specific_tagged_reviews = getIDsByTag("tag y");
|
||||
assert.deepEqual(specific_tagged_reviews, []);
|
||||
});
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
// main.js
|
||||
import {getAllReviewsFromStorage} from "./localStorage.js";
|
||||
import {getIDsByTag, getIDsFromStorage, getReviewFromStorage, getTopIDsFromStorage} from "./localStorage.js";
|
||||
|
||||
// Run the init() function when the page has loaded
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
|
||||
function init() {
|
||||
// Get the reviews from localStorage
|
||||
let reviews = getAllReviewsFromStorage();
|
||||
// Add each reviews to the <main> element
|
||||
addReviewsToDocument(reviews);
|
||||
// Add the event listeners to the form elements
|
||||
//initial population of review container
|
||||
sortAndFilter(false, null);
|
||||
//Add the event listeners to dropdown and search bar
|
||||
initFormHandler();
|
||||
}
|
||||
|
||||
@ -22,21 +20,141 @@ function addReviewsToDocument(reviews) {
|
||||
reviews.forEach(review => {
|
||||
let newReview = document.createElement("review-card");
|
||||
newReview.data = review;
|
||||
//TODO: want to append it to whatever the box is in layout
|
||||
reviewBox.append(newReview);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary event handlers to <form> and the clear storage
|
||||
* <button>.
|
||||
* Adds the necessary event handlers to search-btn and sort
|
||||
*/
|
||||
function initFormHandler() {
|
||||
|
||||
//btn to create form (could be its own function?)
|
||||
let createBtn = document.getElementById("create-btn");
|
||||
createBtn.addEventListener("click", function(){
|
||||
window.location.assign("./CreatePage.html");
|
||||
//grabbing search field
|
||||
let searchField = document.getElementById("search-bar");
|
||||
let searchBtn = document.getElementById("search-btn");
|
||||
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();
|
@ -1,54 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Food Journal</title>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Food Journal</title>
|
||||
|
||||
<!--Add Favicon-->
|
||||
<link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico">
|
||||
<!--Add Favicon-->
|
||||
<link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico">
|
||||
|
||||
<!-- Review Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
|
||||
<!-- Homepage Stylesheets & Scripts -->
|
||||
<link rel="stylesheet" href="./static/homepage.css" />
|
||||
<script src="assets/scripts/main.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="top-bar">
|
||||
<img src ="./assets/images/Logo.png" alt="logo" />
|
||||
<h1> Food Journal </h1>
|
||||
<img src ="./assets/images/Logo.png" alt="logo" />
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="body-container">
|
||||
<div style="width: 20%;"></div>
|
||||
<div style="width: 60%;">
|
||||
<div class="search-bar">
|
||||
<form id="form">
|
||||
<input type="search" id="searching" name="searchBar" placeholder="Search journal...">
|
||||
<button class="click" type="search"> Search </button>
|
||||
<div class="Filter-box">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</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>
|
||||
<!-- 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" />
|
||||
<script src="assets/scripts/main.js" type="module"></script>
|
||||
</head>
|
||||
|
||||
<div class="review-container" id="review-container"></div>
|
||||
<body>
|
||||
<header>
|
||||
<div class="top-bar">
|
||||
<img src="./assets/images/Logo.png" alt="logo" />
|
||||
<h1> Food Journal </h1>
|
||||
<img src="./assets/images/Logo.png" alt="logo" />
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="body-container">
|
||||
<div style="width: 20%;"></div>
|
||||
<div style="width: 60%;">
|
||||
|
||||
<div class="search-bar">
|
||||
|
||||
<form id="form">
|
||||
<label for="sort">Sorting Method:</label>
|
||||
<select id="sort">
|
||||
<option value="recent">Most Recent</option>
|
||||
<option value="top">Top Rated</option>
|
||||
</select>
|
||||
<input type="search" id="search-bar" name="searchBar" placeholder="Search tags...">
|
||||
<button id="clear-search">Clear Search</button>
|
||||
</form>
|
||||
<img src="./assets/images/search_button.png" alt="SEARCH BTN" id="search-btn" />
|
||||
</div>
|
||||
<div style="width: 20%;">
|
||||
|
||||
<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 class="review-container" id="review-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<div style="width: 20%;">
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -17,6 +17,7 @@
|
||||
border: 2px solid rgb(31 41 32);
|
||||
border-radius: 8px;
|
||||
background-color: #f7dfd5;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#d-meal-img {
|
||||
|
@ -52,18 +52,34 @@ body {
|
||||
.search-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-family: "Century Gothic", sans-serif;
|
||||
font-weight: bold;
|
||||
color: #516754;
|
||||
}
|
||||
|
||||
#sort {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.search-bar > form {
|
||||
float: right;
|
||||
padding: 6px 10px;
|
||||
|
||||
/*
|
||||
margin-top: 8px;
|
||||
margin-right: 16px;
|
||||
*/
|
||||
background: rgb(239 183 183);
|
||||
font-size: 17px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#search-btn {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#recent-reviews-text {
|
||||
@ -78,6 +94,17 @@ img#create-btn {
|
||||
align-self: center;
|
||||
padding-left: 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 {
|
||||
|
63
source/sw.js
Normal file
63
source/sw.js
Normal 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});
|
||||
});
|
||||
}));
|
||||
});
|
16
specs/adrs/112922-review-storage.md
Normal file
16
specs/adrs/112922-review-storage.md
Normal 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.
|
15
specs/adrs/112922-reviewpage-session.md
Normal file
15
specs/adrs/112922-reviewpage-session.md
Normal 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.
|
||||
|
13
specs/adrs/112922-tag-review-collision-fix.md
Normal file
13
specs/adrs/112922-tag-review-collision-fix.md
Normal 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.
|
19
specs/adrs/120122-serviceworker-netfirst.md
Normal file
19
specs/adrs/120122-serviceworker-netfirst.md
Normal 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.
|
Loading…
Reference in New Issue
Block a user