Compare commits
143 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e77fe1406c | ||
|
57be3a11ed | ||
|
c381bd1f60 | ||
|
83a0358869 | ||
|
5ba0dab1a1 | ||
|
1dfd8c91a2 | ||
|
ecff5313a1 | ||
|
a12de4e058 | ||
|
9194dd5166 | ||
|
63a7d5ce1f | ||
|
9f9ac19b71 | ||
|
0912ea7956 | ||
|
b5e5a88163 | ||
|
5f1a246e6a | ||
|
4073d2cc4b | ||
|
23657853e5 | ||
|
ec9d0246ca | ||
|
1cbefee26f | ||
|
dd56207892 | ||
|
123057f0f1 | ||
|
50283be4f5 | ||
|
46b1ec4b07 | ||
|
661feafa40 | ||
|
68b0c8edf5 | ||
|
5d2c446551 | ||
|
52bc6a27a4 | ||
|
1db54e9f7a | ||
|
1798835807 | ||
|
1055f7ef26 | ||
|
5a05741e08 | ||
|
4c5132c2b2 | ||
|
49f85d5468 | ||
|
9ef3c59fee | ||
|
069aff88af | ||
|
6d126f6085 | ||
|
948f638cd7 | ||
|
b24b1110f6 | ||
|
eed39f580f | ||
|
259018ca97 | ||
|
142e6d93b6 | ||
|
a5ac2dc940 | ||
|
331f8e731c | ||
|
19bbabe9b4 | ||
|
4336157371 | ||
|
9e82dae202 | ||
|
ccbf060c8c | ||
|
1f832aadd2 | ||
|
6ce15a094e | ||
|
8a1c210515 | ||
|
c412282e04 | ||
|
8c2ad629d2 | ||
|
7b7abad2e6 | ||
|
70c863c378 | ||
|
12e818e079 | ||
|
250617776b | ||
|
9c531771f2 | ||
|
131553ddeb | ||
|
c5cc7bf309 | ||
|
302af4bbd5 | ||
|
3c7b5f7f4e | ||
|
3a7ed2fb16 | ||
|
dd3e0cfefb | ||
|
d284f0ddf2 | ||
|
3db5f62a5c | ||
|
4f2d88db9f | ||
|
c3944d476b | ||
|
8a9a32414e | ||
|
e73a544ab2 | ||
|
ce6b8fcfa6 | ||
|
db3e8dba8d | ||
|
f2edc4dc6a | ||
|
5d331c3800 | ||
|
f340f3ed82 | ||
|
2a6dbc0503 | ||
|
72e2f0fa03 | ||
|
3b1b0cfd57 | ||
|
e7d8eaea59 | ||
|
85d583e1b8 | ||
|
bec66f1385 | ||
|
1c1ac64c58 | ||
|
5668e68594 | ||
|
8338156cb8 | ||
|
3e909ed381 | ||
|
20e9842aea | ||
|
7035fa46ee | ||
|
23ae101f56 | ||
|
1379c22f6f | ||
|
78c07f27d3 | ||
|
c5229c7bc1 | ||
|
20afab9b1f | ||
|
2e95147336 | ||
|
ef5dccfd1a | ||
|
d5be6a2e4d | ||
|
274a05fdc2 | ||
|
65815998e9 | ||
|
cac2b0bf07 | ||
|
82be561bfe | ||
|
b75e4172b4 | ||
|
32323eaf83 | ||
|
f332b27168 | ||
|
0a39039e9c | ||
|
baa722b55e | ||
|
9cf7baf3e5 | ||
|
f6a9fb7fc5 | ||
|
a91c9bc7ef | ||
|
5f84e700d1 | ||
|
46cee253f3 | ||
|
ca89953d41 | ||
|
425bc45453 | ||
|
3422f584f9 | ||
|
89b7319dd8 | ||
|
5e83338668 | ||
|
6493fbd171 | ||
|
35f44049a2 | ||
|
416d26658d | ||
|
6c00246e2d | ||
|
c45aec116b | ||
|
1518fda766 | ||
|
e4755dd074 | ||
|
4eaa1f38bb | ||
|
004e5cc2a2 | ||
|
c66f43f6e9 | ||
|
f9074a2bc6 | ||
|
cf551f932b | ||
|
85dc0544c7 | ||
|
29b065562e | ||
|
02f493e33b | ||
|
0ebf9f8a20 | ||
|
d9c28096fd | ||
|
ad723543e8 | ||
|
7abc995ffe | ||
|
cbe8da609c | ||
|
dd7d876f29 | ||
|
56dceb5edb | ||
|
41905a219b | ||
|
467828d388 | ||
|
540d6563d8 | ||
|
d9297d7c64 | ||
|
81ada40ca3 | ||
|
cc6df228ed | ||
|
2ce3714f8d | ||
|
09dd837081 | ||
|
1d453bdd49 |
2
.github/workflows/css-linting.yml
vendored
@@ -20,4 +20,4 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: sudo npm install
|
||||
- name: Run tests
|
||||
run: sudo npm run lintCSS
|
||||
run: sudo npm run lint-css
|
2
.github/workflows/html-linting.yml
vendored
@@ -20,4 +20,4 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: sudo npm install
|
||||
- name: Run tests
|
||||
run: sudo npm run lintHTML
|
||||
run: sudo npm run lint-html
|
||||
|
2
.github/workflows/js-linting.yml
vendored
@@ -22,4 +22,4 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: sudo npm install
|
||||
- name: Run tests
|
||||
run: sudo npm run lint
|
||||
run: sudo npm run lint-js
|
2
.github/workflows/js-unittest.yml
vendored
@@ -23,5 +23,7 @@ jobs:
|
||||
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 test
|
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"attr-value-not-empty": false
|
||||
"attr-value-not-empty": false,
|
||||
"space-tab-mixed-disabled": "tab"
|
||||
}
|
||||
|
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard"
|
||||
"extends": "stylelint-config-standard",
|
||||
"ignore": ["inside-parens", "param", "value"],
|
||||
"rules":{
|
||||
"indentation": "tab"
|
||||
}
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"liveServer.settings.port": 5501
|
||||
}
|
@@ -1,41 +1,41 @@
|
||||
# Meeting Minutes (11/07/2022)
|
||||
## Team 29: Hackers1995
|
||||
## Meeting Topic: First Sprint
|
||||
Meeting notes for the first sprint
|
||||
|
||||
## Attendance
|
||||
1. Rhea Bhutada
|
||||
2. George Dubinin
|
||||
3. Gavyn Ezell
|
||||
4. Henry Feng
|
||||
5. Kara Hoagland
|
||||
6. Marc Reta
|
||||
7. Sanjit Joseph
|
||||
8. Daniel Hernandez
|
||||
9. Arthur Lu
|
||||
|
||||
## Absentees
|
||||
1. Isaac Otero
|
||||
|
||||
## Meeting Details
|
||||
- When: 11/07/2022 at 6:00PM
|
||||
- Where: CSE Building Second Floor
|
||||
|
||||
## Agenda:
|
||||
- ### Old/Unresolved Business
|
||||
- N/A
|
||||
- ### New Business
|
||||
- The first sprint:
|
||||
- Create more Gitflows and automation. Verify current workflows and actions
|
||||
- Determine interface details for the app (user experience)
|
||||
- Start on the backend
|
||||
- ### Next Meeting's Business
|
||||
|
||||
## Decisions Made
|
||||
- Linting details decided (TABS NOT SPACES)
|
||||
|
||||
## End Time
|
||||
- 11/07/2022 at 8:00PM
|
||||
|
||||
|
||||
|
||||
# Meeting Minutes (11/07/2022)
|
||||
## Team 29: Hackers1995
|
||||
## Meeting Topic: First Sprint
|
||||
Meeting notes for the first sprint
|
||||
|
||||
## Attendance
|
||||
1. Rhea Bhutada
|
||||
2. George Dubinin
|
||||
3. Gavyn Ezell
|
||||
4. Henry Feng
|
||||
5. Kara Hoagland
|
||||
6. Marc Reta
|
||||
7. Sanjit Joseph
|
||||
8. Daniel Hernandez
|
||||
9. Arthur Lu
|
||||
|
||||
## Absentees
|
||||
1. Isaac Otero
|
||||
|
||||
## Meeting Details
|
||||
- When: 11/07/2022 at 6:00PM
|
||||
- Where: CSE Building Second Floor
|
||||
|
||||
## Agenda:
|
||||
- ### Old/Unresolved Business
|
||||
- N/A
|
||||
- ### New Business
|
||||
- The first sprint:
|
||||
- Create more Gitflows and automation. Verify current workflows and actions
|
||||
- Determine interface details for the app (user experience)
|
||||
- Start on the backend
|
||||
- ### Next Meeting's Business
|
||||
|
||||
## Decisions Made
|
||||
- Linting details decided (TABS NOT SPACES)
|
||||
|
||||
## End Time
|
||||
- 11/07/2022 at 8:00PM
|
||||
|
||||
|
||||
|
@@ -1,2 +1,5 @@
|
||||
# cse110-fa22-group29
|
||||
[Team Page Link](https://github.com/cse110-fa22-group29/cse110-fa22-group29/blob/main/admin/team.md)
|
||||
# cse110-fa22-group29
|
||||
|
||||
[Team Page Link](https://github.com/cse110-fa22-group29/cse110-fa22-group29/blob/main/admin/team.md)
|
||||
|
||||
[Food Journal](https://cse110-fa22-group29.github.io/cse110-fa22-group29/)
|
@@ -31,6 +31,9 @@ So far the features listed below have been completed to some degree:
|
||||
- Linting (JS)
|
||||
- Implemented: ction triggers on any PR, uses eslint to perform style enforcement on all JS components
|
||||
- ToDo: trigger workflow only on certain PRs which relate to JS code
|
||||
- Linting (HTML)
|
||||
- Implemented: action triggers on any PR, uses HTMLhint to perform style enforcement on all HTML components
|
||||
- Linting (CSS)
|
||||
- Implemented: action triggers on any PR, uses Stylelint to perform style enforcement on all CSS components
|
||||
|
||||
|
||||
## Planned Features and Timeline
|
||||
## Planned Features and Timeline
|
||||
|
@@ -55,6 +55,14 @@ Overall we feel that sprint 1 was a success with many lessons learned. Our enthu
|
||||
* Scheduled meetings with more notice and keep meetings at a more central location so that more members can attend
|
||||
* Get the unit testing modules up to date
|
||||
* To-do: create a style guide
|
||||
* Heed the styles and documentation (to avoid linter issues)
|
||||
|
||||
## Early Issues
|
||||
* restructure local storage to store individual (key, review) pairs rather than storing data under one key (current schema)
|
||||
* implement a file upload system (think canvas file upload)
|
||||
* add a cuisine attribute for tagging and filtering
|
||||
* Create UI buttons and low fidelity css
|
||||
* Unit test all the above
|
||||
|
||||
## End Time
|
||||
- 11/14/2022 at 5:00PM
|
33
admin/meetings/111622-checkin6.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Meeting Minutes (11/16/2022)
|
||||
## Team 29: Hackers1995
|
||||
## Meeting Topic: Weekly TA Catchup with Gagan
|
||||
We are meeting with Gagan to discuss Checkpoint 1 and Sprint 2 resolutions.
|
||||
|
||||
## Attendance
|
||||
1. Rhea Bhutada
|
||||
2. George Dubinin
|
||||
3. Gagan Gopalaiah
|
||||
4. Kara Hoagland
|
||||
|
||||
## Meeting Details
|
||||
- When: 11/16/2022 at 3:30PM
|
||||
- Where: Zoom
|
||||
|
||||
## Agenda:
|
||||
|
||||
## Discussion Points by Gagan
|
||||
- Updated Gagan on Sprint 1
|
||||
- looked at Girhub actions
|
||||
- looked at the published page so far
|
||||
- discussed retrospective
|
||||
- Upcoming Assignments
|
||||
- we have to come up with a video on the status of our app
|
||||
- ramp up the styling part, so u can brag about the design of the app
|
||||
- this video is supposed to encourage healthy competition
|
||||
- Other Concerns
|
||||
- JSDocs - not primary concern right now
|
||||
- GitHub Pages vs. Netlify
|
||||
- Gagan sees Netlify as more professional and not to difficult to implement
|
||||
|
||||
## End Time
|
||||
- 11/09/2022 at 3:45PM
|
47
admin/meetings/111722-sprint2meeting1.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Meeting Minutes (11/07/2022)
|
||||
## Team 29: Hackers1995
|
||||
## Meeting Topic: First Sprint
|
||||
Meeting notes for the first sprint
|
||||
|
||||
## Attendance
|
||||
1. Rhea Bhutada
|
||||
2. George Dubinin
|
||||
3. Gavyn Ezell
|
||||
4. Henry Feng
|
||||
5. Kara Hoagland
|
||||
6. Marc Reta
|
||||
7. Sanjit Joseph
|
||||
8. Daniel Hernandez
|
||||
9. Arthur Lu
|
||||
10. Isaac Otero
|
||||
|
||||
## Meeting Details
|
||||
- When: 11/17/2022 at 11:30PM
|
||||
- Where: Design & Innovation Building
|
||||
|
||||
## Agenda:
|
||||
- ### Old/Unresolved Business
|
||||
- N/A
|
||||
- ### New Business
|
||||
- Second sprint commences!
|
||||
- Focus on design progress for the project showoff
|
||||
- Cuisine vs Tag identifiers for reviews (both?)
|
||||
- localStorage will hold:
|
||||
- list of active IDs which is updated for very create operation. An ID uniquely identifies a review
|
||||
- value, "nextId" denoting the index of the next available slot for an Id
|
||||
- entries for every single review (javascript object)
|
||||
- a list for every tag that denotes which Ids belong to reviews containing this tag
|
||||
|
||||
End2end tests will rely on specific html element names which include the following:
|
||||
- "create-btn" (located on homepage and used to create a new review)
|
||||
- "submit-btn" (located on form and used to post review)
|
||||
- "update-btn" (located on a specific review page)
|
||||
- "delete-btn" (located on a specific review page)
|
||||
- "tag-add-btn" (located on the review create form)
|
||||
- ### Next Meeting's Business
|
||||
|
||||
## Decisions Made
|
||||
-
|
||||
|
||||
## End Time
|
||||
- 11/17/2022 at 1:00PM
|
36
admin/meetings/112022-sprint2meeting3.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Meeting Minutes (11/20/2022)
|
||||
## Team 29: Hackers1995
|
||||
## Meeting Topic: Second Sprint Meeting 3
|
||||
<what are we working on today>
|
||||
|
||||
## Attendance
|
||||
1. Rhea Bhutada
|
||||
2. George Dubinin
|
||||
3. Gavyn Ezell
|
||||
4. Henry Feng
|
||||
5. Kara Hoagland
|
||||
6. Marc Reta
|
||||
7. Sanjit Joseph
|
||||
8. Daniel Hernandez
|
||||
9. Arthur Lu
|
||||
10. Isaac Otero
|
||||
|
||||
## Meeting Details
|
||||
- When: 11/20/2022 at 1:00PM
|
||||
- Where: CSE Building Second Floor
|
||||
|
||||
## Agenda:
|
||||
- ### Old/Unresolved Business
|
||||
- N/A
|
||||
- ### New Business
|
||||
- Planning for the Agile Steam Status Video
|
||||
- *Present the status of your software*
|
||||
- *Description of current challenges to development*
|
||||
- *Preview of the next sprint and what to look forward to*
|
||||
- ### Next Meeting's Business
|
||||
|
||||
## Decisions Made
|
||||
-
|
||||
|
||||
## End Time
|
||||
- 11/20/2022 at 3:00PM
|
0
admin/meetings/112722-retrospective2.md
Normal file
60
admin/meetings/112722-sprint-2-review.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Sprint 2 Review Meeting Minutes (11/27/2022)
|
||||
## Team 29: Hackers1995
|
||||
## Meeting Topic: Sprint 2 Review
|
||||
We are reviewing the second sprint 2 progress made and highlights
|
||||
|
||||
## Attendance
|
||||
1. Rhea Bhutada
|
||||
2. George Dubinin
|
||||
3. Sanjit Joseph
|
||||
4. Kara Hoagland
|
||||
5. Arthur Lu
|
||||
6. Mark Rheta
|
||||
7. Henry Feng
|
||||
8. Gavyn Etzel
|
||||
9. Sanjit Joseph
|
||||
10. Isaac Otero
|
||||
|
||||
## Meeting Details
|
||||
- When: 11/27/2022 at 4:30PM
|
||||
- Where: Zoom
|
||||
|
||||
## Agenda:
|
||||
Review the second sprint and discuss assiget the writeup for the Agile review assignemnt
|
||||
|
||||
## Sprint 2 REVIEW
|
||||
In collecting feedback for the sprint the leads decided to ask members individually about their experience during sprint 2 to then summarize these responses. Each member was asked 4 questions with their summarized responses below:
|
||||
|
||||
### ➼ What do you think worked well in the first sprint?
|
||||
Communication within the group was improved and our joint study sessions where more productive. The design team got the support they needed to accomplish the majority of their work on the project. The push to emphasize the sub-teams responsible for different tasks turned out to be a great idea and everyone put in a good effort.
|
||||
|
||||
### ➼ What can we improve on for the next sprint?
|
||||
With the vast majority of feature implementation underway the rapid progress created a lot of bugs which otherwise could have been avoid with more careful planning. Some members felt that even though they made a great effort they weren't able to contribute as much as they wanted to. Some of the code documentation fell behind and some design discussions were circumvented because some members where busy. One consequence was that relatively few ADRs were created even though we made many important design decisions during sprint 2.
|
||||
|
||||
### ➼ What was your contribution to the sprint?
|
||||
* Rhea Bhutada: I mainly helped implement the backend for the CRUD features of the app and documentation related to this. This mainly entailed changing the way that we were storing user data in local storage. Additionally, I helped design the form and homepage.
|
||||
* Gavyn Etzel:
|
||||
* Henry Feng: Worked on implementing local image uploading and storing features for updating and creating profiles.
|
||||
* Sanjit: I reimplemented the star ratings since they had some issues and weren’t merged with sprint 1. I fixed a bunch of linting issues that popped up from that as well. I did a fair bit of color palette brainstorming with the team. I also went over our app design for the sprint video and edited that together. Most importantly I put a chef hat on the raccoon
|
||||
* Daniel:
|
||||
* Arthur Lu: Worked on fixing some CI/CD pipeline issues
|
||||
Implemented e2e testing for basic update and delete functionality
|
||||
Helped with fixing the homepage and review page layout
|
||||
Helped with fixing the article tag overflow issue
|
||||
* Marc Rheta: Implemented the e2e testing for reading and create
|
||||
Allowed tabs for CSS/HTML linters
|
||||
* Isaac Otero: I was able to help out with the sprint video for the last sprint and thought of how our page will look like, Started working on homepage.html
|
||||
* George Dubinin: Meeting notes, Repo organization, Front-end (a little), Project Status Review video.
|
||||
* Kara Hoagland: I helped set up the new local storage design, reimplemented the CRUD features using the new local storage design, contributed to the styling, added a default img, backend on the details page
|
||||
|
||||
### ➼ Was there anything blocking your progress in the sprint?
|
||||
A few members got sick over the break and with midterms picking up for other classes some members had trouble dedicting time for the project but everyone still put in a great effort overall.
|
||||
|
||||
## Next Sprint Goals
|
||||
- Resolve the 4 issues open on GitHub right now
|
||||
- Make the project "local first" by creating a cache
|
||||
- Bug fixes and final product adjustments possibly pushed to sprint 4
|
||||
- We aim to keep sprint 3 short (a few days max)
|
||||
- JS docs (we can potentially leave this out with an explanation of where our documentation is)
|
||||
## End Time
|
||||
- 11/27/2022 at 5:00PM
|
12
package.json
@@ -4,16 +4,20 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "mocha --recursive --require mock-local-storage './{,!(node_modules)/**}/*.test.js'",
|
||||
"lint": "eslint '**/*.js'",
|
||||
"fix-style": "eslint --fix **/*.js",
|
||||
"lintHTML": "htmlhint '**/*.html'",
|
||||
"lintCSS": "stylelint '**/*.css'"
|
||||
"lint-js": "eslint **/*.js",
|
||||
"fix-js": "eslint --fix **/*.js",
|
||||
"lint-html": "htmlhint **/*.html",
|
||||
"lint-css": "stylelint **/*.css",
|
||||
"fix-css": "stylelint --fix **/*.css",
|
||||
"http-server": "http-server source"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.27.0",
|
||||
"htmlhint": "1.1.4",
|
||||
"http-server": "",
|
||||
"mocha": "10",
|
||||
"mock-local-storage": "^1.1.23",
|
||||
"puppeteer": "^18.2.1",
|
||||
"stylelint": "14.14.1",
|
||||
"stylelint-config-standard": "^29.0.0"
|
||||
}
|
||||
|
@@ -1,77 +1,93 @@
|
||||
<!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>
|
||||
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
<!--Add Favicon-->
|
||||
<link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico">
|
||||
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
|
||||
|
||||
<!-- 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/CreatePage.css" />
|
||||
<script src="assets/scripts/main.js" type="module"></script>
|
||||
<!-- 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/CreatePage.css" />
|
||||
<link rel="stylesheet" href="./static/Form.css" />
|
||||
<script src="./assets/scripts/CreatePage.js" type="module"></script>
|
||||
</head>
|
||||
|
||||
</head>
|
||||
<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>
|
||||
|
||||
<body>
|
||||
<div class="journal-form">
|
||||
<h1>New Entry</h1>
|
||||
|
||||
<body>
|
||||
<form id="new-food-entry">
|
||||
<fieldset>
|
||||
<legend>Pic:</legend>
|
||||
<label for="mealImage">
|
||||
Source:
|
||||
<input type="text" id="mealImg" name="mealImg">
|
||||
</label>
|
||||
<label for="image-alt">
|
||||
Alt Text:
|
||||
<input type="text" id="imgAlt" name="imgAlt">
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<form id="new-food-entry">
|
||||
|
||||
<fieldset>
|
||||
<legend>PICTURE:</legend>
|
||||
<select id="select" name="select">
|
||||
<option value="file">File Upload</option>
|
||||
<option value="url">From an URL</option>
|
||||
</select>
|
||||
<label for="mealImage" id="source">
|
||||
<input type="file" accept="image/*" id="mealImg" name="mealImg">
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>MEAL NAME:</legend>
|
||||
<label for="Name: "> <input type="text" id="mealName" name="mealName" required> </label>
|
||||
</fieldset>
|
||||
|
||||
<legend> Meal: </legend>
|
||||
<label for="Meal: ">Meal:
|
||||
<input type="text" id="mealName" name="mealName" required>
|
||||
</label>
|
||||
<label for="comments">Comments:
|
||||
<br>
|
||||
<textarea name="comments" id="comments"></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>RESTAURANT NAME:</legend>
|
||||
<label for="Name:"> <input type="text" id="restaurant" name="restaurant" required> </label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>RATING:</legend>
|
||||
<div style="display: flex; justify-content: flex-start; align-items: center;">
|
||||
<div class="rating">
|
||||
<input type="radio" id="s5" name="rating" value="5"/> <label for="s5" id="s5-select"> 5 stars </label>
|
||||
<input type="radio" id="s4" name="rating" value="4"/> <label for="s4" id="s4-select"> 4 stars </label>
|
||||
<input type="radio" id="s3" name="rating" value="3"/> <label for="s3" id="s3-select"> 3 stars </label>
|
||||
<input type="radio" id="s2" name="rating" value="2"/> <label for="s2" id="s2-select"> 2 stars </label>
|
||||
<input type="radio" id="s1" name="rating" value="1"/> <label for="s1" id="s1-select"> 1 star </label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="rating">
|
||||
<legend> Rating: </legend>
|
||||
<input type="radio" id="s5" name="rating" value="5"/> <label for="s5"> 5 stars </label>
|
||||
<input type="radio" id="s4" name="rating" value="4"/> <label for="s4"> 4 stars </label>
|
||||
<input type="radio" id="s3" name="rating" value="3"/> <label for="s3"> 3 stars </label>
|
||||
<input type="radio" id="s2" name="rating" value="2"/> <label for="s2"> 2 stars </label>
|
||||
<input type="radio" id="s1" name="rating" value="1"/> <label for="s1"> 1 star </label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>COMMENTS:</legend>
|
||||
<textarea name="comments" id="comments" rows="5" style="resize: none; width: 100%;"></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Other Info:</legend>
|
||||
<label for="restaurant">
|
||||
Restaurant:
|
||||
<input type="text" id="restaurant" name="restaurant" required>
|
||||
</label>
|
||||
<label for="tag-form">
|
||||
Tags:
|
||||
<input type="text" id="tag-form" name="tag-form">
|
||||
<div class='tag-container' id="tag-container-form">
|
||||
<fieldset>
|
||||
<legend>TAGS: (ex. cuisine, distance, cost, etc)</legend>
|
||||
<input type="text" id="tag-form" name="tag-form">
|
||||
|
||||
</div>
|
||||
<button type="button" id="tagAdd">Add Tag</button>
|
||||
</label>
|
||||
<div class='tag-container' id="tag-container-form">
|
||||
</div>
|
||||
<button type="button" id="tag-add-btn"> + </button>
|
||||
</fieldset>
|
||||
|
||||
</fieldset>
|
||||
<button type="submit" value="Submit">Add Review</button>
|
||||
<button type="button" class="danger">Clear Review Journal</button>
|
||||
</form>
|
||||
</body>
|
||||
<button type="submit" id="save-btn" value="Submit">Save</button>
|
||||
<input type="button" value="Cancel" id="home-btn" onclick="window.location.assign('./index.html')">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -1,77 +1,122 @@
|
||||
<!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>
|
||||
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
<!--Add Favicon-->
|
||||
<link rel="icon" type="image/x-icon" href="./assets/images/icons/favicon.ico">
|
||||
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
|
||||
<!-- 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/ReviewCard.css" />
|
||||
<script src="assets/scripts/ReviewDetails.js" type="module"></script>
|
||||
<!-- 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/ReviewDetails.css" />
|
||||
<link rel="stylesheet" href="./static/Form.css" />
|
||||
<script src="assets/scripts/ReviewDetails.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>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<button type="button" id="update">Update</button>
|
||||
<button type="button" id="delete" class="danger">Delete</button>
|
||||
</main>
|
||||
<!----> <form id="update-food-entry" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Pic:</legend>
|
||||
<label for="mealImage">
|
||||
Source:
|
||||
<input type="text" id="mealImg" name="mealImg">
|
||||
</label>
|
||||
<label for="image-alt">
|
||||
Alt Text:
|
||||
<input type="text" id="imgAlt" name="imgAlt">
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
<legend> Meal: </legend>
|
||||
<label for="Meal: ">Meal:
|
||||
<input type="text" id="mealName" name="mealName" required>
|
||||
</label>
|
||||
<label for="comments">Comments:
|
||||
<br>
|
||||
<textarea name="comments" id="comments"></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="rating">
|
||||
<legend> Rating: </legend>
|
||||
<input type="radio" id="s5" name="rating" value="5"/> <label for="s5"> 5 stars </label>
|
||||
<input type="radio" id="s4" name="rating" value="4"/> <label for="s4"> 4 stars </label>
|
||||
<input type="radio" id="s3" name="rating" value="3"/> <label for="s3"> 3 stars </label>
|
||||
<input type="radio" id="s2" name="rating" value="2"/> <label for="s2"> 2 stars </label>
|
||||
<input type="radio" id="s1" name="rating" value="1"/> <label for="s1"> 1 star </label>
|
||||
</fieldset>
|
||||
<main>
|
||||
<div class="journal-form" id="review-details">
|
||||
<form>
|
||||
<fieldset class = "meal-name">
|
||||
<h1 id="d-mealName" style="font-family: Century Gothic;"></h1>
|
||||
<h1 id="d-restaurant" style="font-family: Century Gothic; font-size: 30px;"></h1>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Other Info:</legend>
|
||||
<label for="restaurant">
|
||||
Restaurant:
|
||||
<input type="text" id="restaurant" name="restaurant" required>
|
||||
</label>
|
||||
<label for="tag-form">
|
||||
Tags:
|
||||
<input type="text" id="tag-form" name="tag-form">
|
||||
<div class='tag-container' id="tag-container-form">
|
||||
|
||||
</div>
|
||||
<button type="button" id="tagAdd">Add Tag</button>
|
||||
</label>
|
||||
|
||||
</fieldset>
|
||||
<button type="submit" value="Submit">Add Review</button>
|
||||
</form>
|
||||
</body>
|
||||
<fieldset class = "meal-pics">
|
||||
<!-- image source -->
|
||||
<img width=40% height=40% id="d-mealImg" style="margin-left: auto; margin-right: auto; display: block;"/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class = "stars-and-comments" style="text-align: center;">
|
||||
<img width=30% height=30% id="d-rating" style="margin-left: auto; margin-right: auto; display: block;"/>
|
||||
<p id = "d-comments"></p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class = "meal-tags">
|
||||
<div class = "tag-container" id="d-tags" style="justify-content: center;"></div>
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!---Navigation Buttons-->
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<img src="./assets/images/home_button_for_interface.png" style="margin: 20px 10px 20px 10px;" id="home-btn" onclick="window.location.assign('./index.html')" height="50" width="50"/>
|
||||
<img src ="./assets/images/edit_button_for_interface.png" style="margin: 20px 10px 20px 10px;" id="update-btn" height="50" width="50"/>
|
||||
<img src ="./assets/images/delete_icon_for_interface.png" style="margin: 20px 10px 20px 10px;" id="delete-btn" class="danger" height="50" width="50"/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="journal-form hidden" id="update-form">
|
||||
<h1>Update Entry</h1>
|
||||
|
||||
<form id="new-food-entry">
|
||||
|
||||
<fieldset>
|
||||
<legend>PICTURE:</legend>
|
||||
<select id="select" name="select">
|
||||
<option value="file">File Upload</option>
|
||||
<option value="url">From an URL</option>
|
||||
</select>
|
||||
<label for="mealImage" id="source">
|
||||
<input type="file" accept="image/*" id="mealImg" name="mealImg">
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>MEAL NAME:</legend>
|
||||
<label for="Name: "> <input type="text" id="mealName" name="mealName" required> </label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>RESTAURANT NAME:</legend>
|
||||
<label for="Name:"> <input type="text" id="restaurant" name="restaurant" required> </label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>RATING:</legend>
|
||||
<div style="display: flex; justify-content: flex-start; align-items: center;">
|
||||
<div class="rating">
|
||||
<input type="radio" id="s5" name="rating" value="5"/> <label for="s5" id="s5-select"> 5 stars </label>
|
||||
<input type="radio" id="s4" name="rating" value="4"/> <label for="s4" id="s4-select"> 4 stars </label>
|
||||
<input type="radio" id="s3" name="rating" value="3"/> <label for="s3" id="s3-select"> 3 stars </label>
|
||||
<input type="radio" id="s2" name="rating" value="2"/> <label for="s2" id="s2-select"> 2 stars </label>
|
||||
<input type="radio" id="s1" name="rating" value="1"/> <label for="s1" id="s1-select"> 1 star </label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>COMMENTS:</legend>
|
||||
<textarea name="comments" id="comments" rows="5" style="resize: none; width: 100%;"></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>TAGS: (ex. cuisine, distance, cost, etc)</legend>
|
||||
<input type="text" id="tag-form" name="tag-form">
|
||||
|
||||
<div class='tag-container' id="tag-container-form">
|
||||
</div>
|
||||
<button type="button" id="tag-add-btn"> + </button>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" id="save-btn" value="Submit">Save</button>
|
||||
<input type="button" value="Cancel" id="home-btn" onclick="window.location.assign('./index.html')">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
source/assets/images/Grouppink.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
source/assets/images/Logo.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 311 KiB |
BIN
source/assets/images/delete_icon_for_interface.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
source/assets/images/edit_button_for_interface.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
source/assets/images/favicon.ico
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
source/assets/images/home_button_for_interface.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB |
110
source/assets/scripts/CreatePage.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { newReviewToStorage } from "./localStorage.js";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
|
||||
function init() {
|
||||
// get next id
|
||||
|
||||
// creates the key
|
||||
initFormHandler();
|
||||
|
||||
}
|
||||
|
||||
function initFormHandler() {
|
||||
|
||||
//accessing form components
|
||||
let tagContainer = document.getElementById("tag-container-form");
|
||||
let form = document.querySelector("form");
|
||||
|
||||
/*
|
||||
* change the input source of the image between local file and URL
|
||||
* depending on user's selection
|
||||
*/
|
||||
let select = document.getElementById("select");
|
||||
select.addEventListener("change", function() {
|
||||
const input = document.getElementById("source");
|
||||
|
||||
if (select.value == "file") {
|
||||
input.innerHTML = `
|
||||
Source:
|
||||
<input type="file" accept="image/*" id="mealImg" name="mealImg">
|
||||
`;
|
||||
}
|
||||
//TODO: change to photo taking for sprint 3
|
||||
else {
|
||||
input.innerHTML = `
|
||||
Source:
|
||||
<input type="text" id="mealImg" name="mealImg">
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
//addressing sourcing image from local file
|
||||
let imgDataURL = "";
|
||||
document.getElementById("mealImg").addEventListener("change", function() {
|
||||
const reader = new FileReader();
|
||||
|
||||
//store image data URL after successful image load
|
||||
reader.addEventListener("load", ()=>{
|
||||
imgDataURL = reader.result;
|
||||
}, false);
|
||||
|
||||
//convert image file into data URL for local storage
|
||||
reader.readAsDataURL(document.getElementById("mealImg").files[0]);
|
||||
});
|
||||
|
||||
form.addEventListener("submit", function(e){
|
||||
/*
|
||||
* User submits the form for their review.
|
||||
* We create reviewCard and put in storage
|
||||
*/
|
||||
e.preventDefault();
|
||||
let formData = new FormData(form);
|
||||
let reviewObject = {};
|
||||
for (let [key, value] of formData) {
|
||||
console.log(`${key}`);
|
||||
console.log(`${value}`);
|
||||
if (`${key}` !== "tag-form") {
|
||||
reviewObject[`${key}`] = `${value}`;
|
||||
}
|
||||
if (`${key}` === "mealImg" && select.value == "file") {
|
||||
reviewObject["mealImg"] = imgDataURL;
|
||||
}
|
||||
}
|
||||
if(reviewObject["rating"] != null){
|
||||
reviewObject["tags"] = [];
|
||||
|
||||
let tags = document.querySelectorAll(".tag");
|
||||
for(let i = 0; i < tags.length; i ++) {
|
||||
reviewObject["tags"].push(tags[i].innerHTML);
|
||||
tagContainer.removeChild(tags[i]);
|
||||
}
|
||||
|
||||
let nextReviewId = newReviewToStorage(reviewObject);
|
||||
sessionStorage.setItem("currID", JSON.stringify(nextReviewId));
|
||||
|
||||
window.location.assign("./ReviewDetails.html");
|
||||
} else{
|
||||
window.alert("NO! FILL IN STARS");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
let tagAddBtn = document.getElementById("tag-add-btn");
|
||||
tagAddBtn.addEventListener("click", ()=> {
|
||||
let tagField = document.getElementById("tag-form");
|
||||
if (tagField.value.length > 0) {
|
||||
let tagLabel = document.createElement("label");
|
||||
tagLabel.innerHTML = tagField.value;
|
||||
tagLabel.setAttribute("class","tag");
|
||||
tagLabel.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(tagLabel);
|
||||
});
|
||||
|
||||
tagContainer.append(tagLabel);
|
||||
tagField.value = "";
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
@@ -6,91 +6,108 @@ class ReviewCard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
||||
let shadowEl = this.attachShadow({mode:"open"});
|
||||
|
||||
let articleEl = document.createElement("article");
|
||||
|
||||
let styleEl = document.createElement("style");
|
||||
styleEl.textContent = `
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
article {
|
||||
align-items: center;
|
||||
border: 1px solid rgb(223, 225, 229);
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
grid-template-rows: 118px 56px 14px 18px 15px 36px;
|
||||
height: auto;
|
||||
row-gap: 5px;
|
||||
padding: 0 16px 16px 16px;
|
||||
width: 178px;
|
||||
}
|
||||
|
||||
div.rating {
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
div.rating>img {
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
object-fit: scale-down;
|
||||
width: 78px;
|
||||
}
|
||||
|
||||
article>img {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
height: 118px;
|
||||
object-fit: cover;
|
||||
margin-left: -16px;
|
||||
width: calc(100% + 32px);
|
||||
}
|
||||
|
||||
label.restaurant-name {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
label.meal-name {
|
||||
display: -webkit-box;
|
||||
font-size: 16px;
|
||||
height: 36px;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
label:not(.meal-name),
|
||||
span,
|
||||
time {
|
||||
color: #70757A;
|
||||
font-size: 12px;
|
||||
}
|
||||
`;
|
||||
* {
|
||||
font-family: Century Gothic;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
article {
|
||||
align-items: center;
|
||||
border: 2px solid rgb(31, 41, 32);
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
grid-template-rows: 118px 56px 14px 18px 15px 36px;
|
||||
height: auto;
|
||||
row-gap: 5px;
|
||||
padding: 0 16px 16px 16px;
|
||||
width: 178px;
|
||||
margin: 8px 8px 8px 8px;
|
||||
}
|
||||
|
||||
div.rating {
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
div.rating>img {
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
object-fit: scale-down;
|
||||
width: 78px;
|
||||
}
|
||||
|
||||
article>img {
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
height: 119px;
|
||||
object-fit: cover;
|
||||
margin-left: -16px;
|
||||
width: calc(100% + 32px);
|
||||
}
|
||||
|
||||
label.restaurant-name {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
label.meal-name {
|
||||
display: -webkit-box;
|
||||
font-size: 16px;
|
||||
height: 36px;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
label:not(.meal-name),
|
||||
span,
|
||||
time {
|
||||
color: #70757A;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.a-tag {
|
||||
background-color:#94da97;
|
||||
border-radius: 7px;
|
||||
color: #94da97;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
margin: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
`;
|
||||
articleEl.append(styleEl);
|
||||
shadowEl.append(articleEl);
|
||||
this.shadowEl = shadowEl;
|
||||
//attach event listener to each recipe-card
|
||||
this.addEventListener("click", (event) => {
|
||||
console.log(event.target);
|
||||
console.log(event.target.data);
|
||||
console.log(event.target.reviewId);
|
||||
//Option 1: sending current data to second html page using localStorage (could also just store index)
|
||||
sessionStorage.setItem("current", JSON.stringify(event.target.data));
|
||||
sessionStorage.setItem("currID", JSON.stringify(event.target.data.reviewID));
|
||||
window.location.assign("./ReviewDetails.html");
|
||||
/*
|
||||
//Option 2: sending current data to second html page using string query w/ url (currently not storing value)
|
||||
@@ -117,7 +134,6 @@ class ReviewCard extends HTMLElement {
|
||||
* following format:
|
||||
* {
|
||||
* "mealImg": "string",
|
||||
* "imgAlt": "string",
|
||||
* "mealName": "string",
|
||||
* "comments": "string",
|
||||
* "rating": number,
|
||||
@@ -133,12 +149,17 @@ class ReviewCard extends HTMLElement {
|
||||
let articleEl = this.shadowEl.querySelector("article");
|
||||
|
||||
// setting the article elements for the review card
|
||||
this.reviewID = data["reviewID"];
|
||||
|
||||
//image setup
|
||||
let mealImg = document.createElement("img");
|
||||
mealImg.setAttribute("id", "a-mealImg");
|
||||
mealImg.setAttribute("alt","Meal Photo Corrupted");
|
||||
mealImg.setAttribute("src",data["mealImg"]);
|
||||
mealImg.setAttribute("alt",data["imgAlt"]);
|
||||
mealImg.addEventListener("error", function(e) {
|
||||
mealImg.setAttribute("src", "./assets/images/plate_with_cutlery.png");
|
||||
e.onerror = null;
|
||||
});
|
||||
|
||||
//meal name setup
|
||||
let mealLabel = document.createElement("label");
|
||||
@@ -147,26 +168,6 @@ class ReviewCard extends HTMLElement {
|
||||
mealLabel.innerHTML = data["mealName"];
|
||||
|
||||
//restaurant name setup
|
||||
/*
|
||||
//review page link
|
||||
//giving it functionality to save the review card's info to session storage for loading the review page
|
||||
let reviewLink = document.createElement('a');
|
||||
reviewLink.setAttribute('href','./review.html')
|
||||
reviewLink.innerHTML = 'review page'
|
||||
reviewLink.addEventListener('click', () => {
|
||||
sessionStorage.clear();
|
||||
let currReview = {
|
||||
"imgSrc": data['imgSrc'],
|
||||
"imgAlt": data['imgAlt'],
|
||||
"mealName": data['mealName'],
|
||||
"restaurant": data['restaurant'],
|
||||
"comments": data['comments'],
|
||||
"rating": data['rating'],
|
||||
"tags": data['tags']
|
||||
}
|
||||
sessionStorage.setItem('currReview', JSON.stringify(currReview));
|
||||
});
|
||||
*/
|
||||
let restaurantLabel = document.createElement("label");
|
||||
restaurantLabel.setAttribute("id", "a-restaurant");
|
||||
restaurantLabel.setAttribute("class","restaurant-name");
|
||||
@@ -183,7 +184,7 @@ class ReviewCard extends HTMLElement {
|
||||
ratingDiv.setAttribute("class", "rating");
|
||||
let starsImg = document.createElement("img");
|
||||
starsImg.setAttribute("id", "a-rating");
|
||||
starsImg.setAttribute("src", "./assets/images/icons/"+data["rating"]+"-star.svg");
|
||||
starsImg.setAttribute("src", "./assets/images/"+data["rating"]+"-star.svg");
|
||||
starsImg.setAttribute("alt", data["rating"] +" stars");
|
||||
starsImg.setAttribute("num", data["rating"]);
|
||||
ratingDiv.append(starsImg);
|
||||
@@ -196,15 +197,16 @@ class ReviewCard extends HTMLElement {
|
||||
if(data["tags"]){
|
||||
for (let i = 0; i < data["tags"].length; i++) {
|
||||
let newTag = document.createElement("label");
|
||||
newTag.setAttribute("class","tag");
|
||||
newTag.innerHTML = data["tags"][i] + " ";
|
||||
newTag.setAttribute("class","a-tag");
|
||||
newTag.innerHTML = data["tags"][i];
|
||||
tagContainer.append(newTag);
|
||||
}
|
||||
}
|
||||
|
||||
//adding final ID to data!
|
||||
|
||||
articleEl.append(mealImg);
|
||||
articleEl.append(mealLabel);
|
||||
//articleEl.append(reviewLink)
|
||||
articleEl.append(restaurantLabel);
|
||||
articleEl.append(ratingDiv);
|
||||
articleEl.append(tagContainer);
|
||||
@@ -224,7 +226,6 @@ class ReviewCard extends HTMLElement {
|
||||
* following format:
|
||||
* {
|
||||
* "mealImg": "string",
|
||||
* "imgAlt": "string",
|
||||
* "mealName": "string",
|
||||
* "comments": "string",
|
||||
* "rating": number,
|
||||
@@ -237,11 +238,11 @@ class ReviewCard extends HTMLElement {
|
||||
let dataContainer = {};
|
||||
|
||||
// getting the article elements for the review card
|
||||
dataContainer["reviewID"] = this.reviewID;
|
||||
|
||||
//get image
|
||||
let mealImg = this.shadowEl.getElementById("a-mealImg");
|
||||
dataContainer["mealImg"] = mealImg.getAttribute("src");
|
||||
dataContainer["imgAlt"] = mealImg.getAttribute("alt");
|
||||
|
||||
//get meal name
|
||||
let mealLabel = this.shadowEl.getElementById("a-mealName");
|
||||
|
@@ -1,124 +1,194 @@
|
||||
//reviewDetails.js
|
||||
import {getReviewsFromStorage, saveReviewsToStorage} from "./localStorage.js";
|
||||
import {deleteReviewFromStorage, getReviewFromStorage, updateReviewToStorage} from "./localStorage.js";
|
||||
|
||||
// Run the init() function when the page has loaded
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
|
||||
function init(){
|
||||
setupInfo();
|
||||
setupDelete();
|
||||
setupUpdate();
|
||||
}
|
||||
|
||||
function setupInfo(){
|
||||
let currID = JSON.parse(sessionStorage.getItem("currID"));
|
||||
let currReview = getReviewFromStorage(currID);
|
||||
|
||||
//meal image
|
||||
let mealImg = document.getElementById("d-mealImg");
|
||||
mealImg.setAttribute("src",currReview["mealImg"]);
|
||||
mealImg.addEventListener("error", function(e) {
|
||||
mealImg.setAttribute("src", "./assets/images/plate_with_cutlery.png");
|
||||
e.onerror = null;
|
||||
});
|
||||
|
||||
//meal name
|
||||
let mealLabel = document.getElementById("d-mealName");
|
||||
mealLabel.innerHTML = currReview["mealName"];
|
||||
|
||||
//restaurant name
|
||||
let restaurantLabel = document.getElementById("d-restaurant");
|
||||
restaurantLabel.innerHTML = currReview["restaurant"];
|
||||
|
||||
//comments
|
||||
let comments = document.getElementById("d-comments");
|
||||
comments.innerText = currReview["comments"];
|
||||
|
||||
//rating
|
||||
let starsImg = document.getElementById("d-rating");
|
||||
starsImg.setAttribute("src", "./assets/images/"+currReview["rating"]+"-star.svg");
|
||||
starsImg.setAttribute("alt", currReview["rating"] +" stars");
|
||||
|
||||
//tags
|
||||
let tagContainer = document.getElementById("d-tags");
|
||||
if(currReview["tags"]){
|
||||
for (let i = 0; i < currReview["tags"].length; i++) {
|
||||
let newTag = document.createElement("label");
|
||||
newTag.setAttribute("class","d-tag");
|
||||
newTag.innerHTML = currReview["tags"][i];
|
||||
tagContainer.append(newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupDelete(){
|
||||
let deleteBtn = document.getElementById("delete");
|
||||
let reviews = getReviewsFromStorage();
|
||||
let current = JSON.parse(sessionStorage.getItem("current"));
|
||||
let deleteBtn = document.getElementById("delete-btn");
|
||||
let currID = JSON.parse(sessionStorage.getItem("currID"));
|
||||
deleteBtn.addEventListener("click", function(){
|
||||
if(window.confirm("Are you sure you want to delete this entry?")){
|
||||
//delete function
|
||||
if(current){
|
||||
console.log(current);
|
||||
for(let i = 0; i < reviews.length; i++){
|
||||
console.log(reviews[i]);
|
||||
if(reviews[i]["mealName"] == current["mealName"] && reviews[i]["restaurant"] == current["restaurant"]){
|
||||
console.log("match found");
|
||||
reviews.splice(i,1);
|
||||
saveReviewsToStorage(reviews);
|
||||
sessionStorage.removeItem("current");
|
||||
window.location.assign("./index.html");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteReviewFromStorage(currID);
|
||||
sessionStorage.removeItem("currID");
|
||||
window.location.assign("./index.html");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupUpdate(){
|
||||
let updateBtn = document.getElementById("update");
|
||||
let reviews = getReviewsFromStorage();
|
||||
let current = JSON.parse(sessionStorage.getItem("current"));
|
||||
let form = document.getElementById("update-food-entry");
|
||||
let updateBtn = document.getElementById("update-btn");
|
||||
let currID = JSON.parse(sessionStorage.getItem("currID"));
|
||||
let currReview = getReviewFromStorage(currID);
|
||||
let form = document.getElementById("new-food-entry");
|
||||
let updateDiv = document.getElementById("update-form");
|
||||
updateBtn.addEventListener("click", function(){
|
||||
//update function
|
||||
if(current){
|
||||
console.log(current);
|
||||
form.style.display = "block";
|
||||
let tagContainer = document.getElementById("tag-container-form");
|
||||
console.log(document.querySelectorAll("#update-food-entry input"));
|
||||
|
||||
//Set value of each input element to current's values
|
||||
document.getElementById("mealImg").defaultValue = current["mealImg"];
|
||||
document.getElementById("imgAlt").defaultValue = current["imgAlt"];
|
||||
document.getElementById("mealName").defaultValue = current["mealName"];
|
||||
document.getElementById("comments").textContent = current["comments"];
|
||||
document.getElementById("rating-" + `${current["rating"]}`).checked = true;
|
||||
document.getElementById("restaurant").defaultValue = current["restaurant"];
|
||||
updateDiv.classList.remove("hidden");
|
||||
|
||||
if(current["tags"]){
|
||||
for (let i = 0; i < current["tags"].length; i++) {
|
||||
let newTag = document.createElement("label");
|
||||
newTag.setAttribute("class","tag");
|
||||
newTag.innerHTML = current["tags"][i] + " ";
|
||||
newTag.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(newTag);
|
||||
});
|
||||
tagContainer.append(newTag);
|
||||
let tagContainer = document.getElementById("tag-container-form");
|
||||
|
||||
//Set value of each input element to current's values
|
||||
document.getElementById("mealImg").defaultValue = currReview["mealImg"];
|
||||
document.getElementById("mealName").defaultValue = currReview["mealName"];
|
||||
document.getElementById("comments").textContent = currReview["comments"];
|
||||
document.getElementById("s" + `${currReview["rating"]}`).checked = true;
|
||||
document.getElementById("restaurant").defaultValue = currReview["restaurant"];
|
||||
|
||||
if(currReview["tags"]){
|
||||
while (tagContainer.firstChild) {
|
||||
tagContainer.removeChild(tagContainer.firstChild);
|
||||
}
|
||||
for (let i = 0; i < currReview["tags"].length; i++) {
|
||||
let newTag = document.createElement("label");
|
||||
newTag.setAttribute("class","tag");
|
||||
newTag.innerHTML = currReview["tags"][i];
|
||||
newTag.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(newTag);
|
||||
});
|
||||
tagContainer.append(newTag);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* change the input source of the image between local file and URL
|
||||
* depending on user's selection
|
||||
*/
|
||||
let select = document.getElementById("select");
|
||||
select.addEventListener("change", function() {
|
||||
const input = document.getElementById("source");
|
||||
|
||||
if (select.value == "file") {
|
||||
input.innerHTML = `
|
||||
Source:
|
||||
<input type="file" accept="image/*" id="mealImg" name="mealImg">
|
||||
`;
|
||||
}
|
||||
//TODO: change to photo taking for sprint 3
|
||||
else {
|
||||
input.innerHTML = `
|
||||
Source:
|
||||
<input type="text" id="mealImg" name="mealImg">
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
//addressing sourcing image from local file
|
||||
let imgDataURL = "";
|
||||
document.getElementById("mealImg").addEventListener("change", function() {
|
||||
console.log("reading used");
|
||||
const reader = new FileReader();
|
||||
|
||||
//store image data URL after successful image load
|
||||
reader.addEventListener("load", ()=>{
|
||||
imgDataURL = reader.result;
|
||||
}, false);
|
||||
|
||||
//convert image file into data URL for local storage
|
||||
reader.readAsDataURL(document.getElementById("mealImg").files[0]);
|
||||
});
|
||||
|
||||
|
||||
//Take formdata values as newData when submit
|
||||
form.addEventListener("submit", function(){
|
||||
/*
|
||||
* User submits the form for their review.
|
||||
* We create reviewCard and put in storage
|
||||
*/
|
||||
let formData = new FormData(form);
|
||||
let newData = {};
|
||||
for (let [key, value] of formData) {
|
||||
console.log(`${key}`);
|
||||
console.log(`${value}`);
|
||||
if (`${key}` !== "tag-form") {
|
||||
newData[`${key}`] = `${value}`;
|
||||
}
|
||||
//Account for the case where image is not updated
|
||||
if (`${key}` === "mealImg" && document.getElementById("mealImg").value === "") {
|
||||
newData["mealImg"] = currReview["mealImg"];
|
||||
}
|
||||
else if (`${key}` === "mealImg" && select.value == "file") {
|
||||
newData["mealImg"] = imgDataURL;
|
||||
}
|
||||
}
|
||||
//Take formdata values as newData when submit
|
||||
form.addEventListener("submit", function(){
|
||||
/*
|
||||
* User submits the form for their review.
|
||||
* We create reviewCard and put in storage
|
||||
*/
|
||||
let formData = new FormData(form);
|
||||
let newData = {};
|
||||
for (let [key, value] of formData) {
|
||||
console.log(`${key}`);
|
||||
console.log(`${value}`);
|
||||
if (`${key}` !== "tag-form") {
|
||||
newData[`${key}`] = `${value}`;
|
||||
}
|
||||
}
|
||||
newData["tags"] = [];
|
||||
|
||||
let tags = document.querySelectorAll(".tag");
|
||||
for(let i = 0; i < tags.length; i ++) {
|
||||
newData["tags"].push(tags[i].innerHTML);
|
||||
tagContainer.removeChild(tags[i]);
|
||||
}
|
||||
newData["tags"] = [];
|
||||
|
||||
let tags = document.querySelectorAll(".tag");
|
||||
for(let i = 0; i < tags.length; i ++) {
|
||||
newData["tags"].push(tags[i].innerHTML);
|
||||
tagContainer.removeChild(tags[i]);
|
||||
}
|
||||
|
||||
for(let i = 0; i < reviews.length; i++){
|
||||
console.log(reviews[i]);
|
||||
if(reviews[i]["mealName"] == current["mealName"] && reviews[i]["restaurant"] == current["restaurant"]){
|
||||
console.log("match found");
|
||||
reviews.splice(i,1,newData);
|
||||
saveReviewsToStorage(reviews);
|
||||
sessionStorage.setItem("current", JSON.stringify(newData));
|
||||
break;
|
||||
}
|
||||
}
|
||||
newData["reviewID"] = currID;
|
||||
|
||||
form.style.display = "none";
|
||||
updateReviewToStorage(currID, newData);
|
||||
|
||||
});
|
||||
updateDiv.classList.add("hidden");
|
||||
|
||||
let tagAddBtn = document.getElementById("tagAdd");
|
||||
tagAddBtn.addEventListener("click", ()=> {
|
||||
let tagField = document.getElementById("tag-form");
|
||||
if (tagField.value.length > 0) {
|
||||
let tagLabel = document.createElement("label");
|
||||
tagLabel.innerHTML = tagField.value;
|
||||
tagLabel.setAttribute("class","tag");
|
||||
tagLabel.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(tagLabel);
|
||||
});
|
||||
|
||||
tagContainer.append(tagLabel);
|
||||
tagField.value = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let tagAddBtn = document.getElementById("tag-add-btn");
|
||||
tagAddBtn.addEventListener("click", ()=> {
|
||||
let tagField = document.getElementById("tag-form");
|
||||
if (tagField.value.length > 0) {
|
||||
let tagLabel = document.createElement("label");
|
||||
tagLabel.innerHTML = tagField.value;
|
||||
tagLabel.setAttribute("class","tag");
|
||||
tagLabel.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(tagLabel);
|
||||
});
|
||||
|
||||
tagContainer.append(tagLabel);
|
||||
tagField.value = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -1,13 +0,0 @@
|
||||
// Run the init() function when the page has loaded
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
|
||||
function init() {
|
||||
let result = sessionStorage.getItem("currReview");
|
||||
|
||||
let main = document.querySelector("main");
|
||||
|
||||
main.innerHTML = result;
|
||||
let p = document.createElement("p");
|
||||
p.innerHTML = JSON.parse(result)["comments"];
|
||||
main.append(p);
|
||||
}
|
73
source/assets/scripts/appTestHelpers.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import {strict as assert} from "node:assert";
|
||||
|
||||
/**
|
||||
* Fills out a create or update review form
|
||||
* @param {Object} page the page object which contains the create or update form
|
||||
* @param {Object} review review data to input into the form
|
||||
*/
|
||||
export async function setReviewForm(page, review) {
|
||||
|
||||
// Set text fields
|
||||
await page.$eval("#mealName", (el, value) => el.value = value, review.mealName);
|
||||
await page.$eval("#comments", (el, value) => el.value = value, review.comments);
|
||||
await page.$eval("#restaurant", (el, value) => el.value = value, review.restaurant);
|
||||
|
||||
// Get all tag elements and click them to delete them
|
||||
let tag_items = await page.$$(".tag");
|
||||
if(tag_items !== null){
|
||||
for(let i = 0; i < tag_items.length; i++){
|
||||
await tag_items[i].click();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the button needed to add new tags
|
||||
let tag_btn = await page.$("#tag-add-btn");
|
||||
for(let i = 0; i < review.tags.length; i++){
|
||||
await page.$eval("#tag-form", (el, value) => el.value = value, review.tags[i]);
|
||||
await tag_btn.click();
|
||||
}
|
||||
|
||||
// Select a new rating
|
||||
let rating_select = await page.$(`#s${review.rating}-select`);
|
||||
await rating_select.click({delay: 100});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a page or shadowDOM for correct element text or src values
|
||||
* @param {Object} root page or shodowDOM to test
|
||||
* @param {string} prefix prefix character for element IDs
|
||||
* @param {Object} expected values for each element
|
||||
*/
|
||||
export async function checkCorrectness(root, prefix, expected){
|
||||
// Get the review image and check src
|
||||
let img = await root.$(`#${prefix}-mealImg`);
|
||||
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_text = await title.getProperty("innerText");
|
||||
let comment = await root.$(`#${prefix}-comments`);
|
||||
let comment_text = await comment.getProperty("innerText");
|
||||
let restaurant = await root.$(`#${prefix}-restaurant`);
|
||||
let restaurant_text = await restaurant.getProperty("innerText");
|
||||
|
||||
// Check title, comment, and restaurant
|
||||
assert.strictEqual(await title_text.jsonValue(), expected.mealName);
|
||||
assert.strictEqual(await comment_text.jsonValue(), expected.comments);
|
||||
assert.strictEqual(await restaurant_text.jsonValue(), expected.restaurant);
|
||||
|
||||
// Check tags
|
||||
let tags = await root.$$(`.${prefix}-tag`);
|
||||
assert.strictEqual(await tags.length, expected.tags.length);
|
||||
for(let i = 0; i < expected.tags.length; i++){
|
||||
let tag_text = await tags[i].getProperty("innerText");
|
||||
assert.strictEqual(await tag_text.jsonValue(), expected.tags[i]);
|
||||
}
|
||||
|
||||
// Check stars
|
||||
let stars = await root.$(`#${prefix}-rating`);
|
||||
let stars_src = await stars.getProperty("src");
|
||||
assert.strictEqual(await stars_src.jsonValue(), expected.rating);
|
||||
}
|
@@ -1,19 +1,78 @@
|
||||
/**
|
||||
* @returns {Array<Object>} An array of reviews found in localStorage
|
||||
* Creates a new review to storage and performs related meta tasks
|
||||
* @param {Object} review to store
|
||||
* @return {number} ID of the newly added review
|
||||
*/
|
||||
export function getReviewsFromStorage() {
|
||||
let result = JSON.parse(localStorage.getItem("reviews"));
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return new Array(0);
|
||||
export function newReviewToStorage(review){
|
||||
//grabbing the nextID, and putting our review object in storage associated with the ID
|
||||
let nextReviewId = JSON.parse(localStorage.getItem("nextID"));
|
||||
review["reviewID"] = nextReviewId;
|
||||
|
||||
// set the review entry to the review object
|
||||
localStorage.setItem(`review${nextReviewId}`, JSON.stringify(review));
|
||||
|
||||
//updating our activeIDS list
|
||||
let tempIdArr = JSON.parse(localStorage.getItem("activeIDS"));
|
||||
tempIdArr.push(nextReviewId);
|
||||
localStorage.setItem("activeIDS", JSON.stringify(tempIdArr));
|
||||
|
||||
//increment nextID for next review creation
|
||||
localStorage.setItem("nextID", JSON.stringify(nextReviewId + 1));
|
||||
|
||||
return nextReviewId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in an array of reviews, converts it to a string, and then
|
||||
* saves that string to 'reviews' in localStorage
|
||||
* @param {Array<Object>} reviews An array of reviews
|
||||
* Gets a single review by ID from storage
|
||||
* @param {string} ID of the review to get
|
||||
* @returns {Object} review object corresponding to param ID
|
||||
*/
|
||||
export function saveReviewsToStorage(reviews) {
|
||||
localStorage.setItem("reviews", JSON.stringify(reviews));
|
||||
export function getReviewFromStorage(ID){
|
||||
return JSON.parse(localStorage.getItem(`review${ID}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single review by ID to storage
|
||||
* @param {string} ID of review to update
|
||||
* @param {Object} review to store
|
||||
*/
|
||||
export function updateReviewToStorage(ID, review){
|
||||
// set the review entry with ID to the review object
|
||||
localStorage.setItem(`review${ID}`, JSON.stringify(review));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a review by ID from storage
|
||||
* @param {string} ID of the review to delete
|
||||
*/
|
||||
export function deleteReviewFromStorage(ID){
|
||||
let activeIDS = JSON.parse(localStorage.getItem("activeIDS"));
|
||||
|
||||
for (let i in activeIDS) {
|
||||
if (activeIDS[i] == ID) {
|
||||
activeIDS.splice(i,1);
|
||||
localStorage.setItem("activeIDS", JSON.stringify(activeIDS));
|
||||
localStorage.removeItem(`review${ID}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`could not find review${ID} in localStorage`);
|
||||
}
|
||||
|
||||
// legacy function
|
||||
export function getAllReviewsFromStorage() {
|
||||
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));
|
||||
}
|
||||
//iterate thru activeIDS
|
||||
let activeIDS = JSON.parse(localStorage.getItem("activeIDS"));
|
||||
let reviews = [];
|
||||
for (let i = 0; i < activeIDS.length; i++) {
|
||||
let currReview = JSON.parse(localStorage.getItem(`review${activeIDS[i]}`));
|
||||
reviews.push(currReview);
|
||||
}
|
||||
return reviews;
|
||||
}
|
@@ -1,48 +1,103 @@
|
||||
import {strict as assert} from "node:assert";
|
||||
import {describe, it, beforeEach} from "mocha";
|
||||
import {saveReviewsToStorage, getReviewsFromStorage} from "./localStorage.js";
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
import {describe, it, before, after} from "mocha";
|
||||
import {newReviewToStorage, getReviewFromStorage, updateReviewToStorage, deleteReviewFromStorage, getAllReviewsFromStorage} from "./localStorage.js";
|
||||
|
||||
describe("test app localStorage interaction", () => {
|
||||
it("get after init", () => {
|
||||
assert.deepEqual(getReviewsFromStorage(), []);
|
||||
|
||||
before(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
it("store one then get", () => {
|
||||
let reviews = [{
|
||||
|
||||
it("test localStorage state after init", () => {
|
||||
assert.deepEqual(getAllReviewsFromStorage(), []);
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem("activeIDS")), []);
|
||||
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 0);
|
||||
});
|
||||
|
||||
it("test localStorage state after adding one review", () => {
|
||||
let review = {
|
||||
"imgSrc": "sample src",
|
||||
"imgAlt": "sample alt",
|
||||
"mealName": "sample name",
|
||||
"restaurant": "sample restaurant",
|
||||
"rating": 5,
|
||||
"tags": ["tag 1", "tag 2", "tag 3"]
|
||||
}];
|
||||
};
|
||||
|
||||
saveReviewsToStorage(reviews);
|
||||
assert.deepEqual(getReviewsFromStorage(), reviews);
|
||||
newReviewToStorage(review);
|
||||
|
||||
review.reviewID = 0;
|
||||
|
||||
assert.deepEqual(getAllReviewsFromStorage(), [review]);
|
||||
assert.deepEqual(getReviewFromStorage(0), review);
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem("activeIDS")), [0]);
|
||||
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 1);
|
||||
});
|
||||
it("repeated store one more and get", () => {
|
||||
let reviews = [];
|
||||
|
||||
assert.deepEqual(getReviewsFromStorage(), reviews);
|
||||
it("test localStorage state during adding 999 reviews", () => {
|
||||
let reviews = getAllReviewsFromStorage();
|
||||
let ids = [0];
|
||||
|
||||
for(let i = 1; i < 1000; i++){
|
||||
ids.push(i);
|
||||
let new_review = {
|
||||
"imgSrc": `sample src ${i}`,
|
||||
"mealName": `sample name ${i}`,
|
||||
"restaurant": `sample restaurant ${i}`,
|
||||
"rating": i,
|
||||
"tags": [`tag ${3*i}`, `tag ${3*i + 1}`, `tag ${3*i + 2}`]
|
||||
};
|
||||
|
||||
newReviewToStorage(new_review);
|
||||
|
||||
new_review.reviewID = i;
|
||||
reviews.push(new_review);
|
||||
|
||||
assert.deepEqual(getAllReviewsFromStorage(), reviews);
|
||||
assert.deepEqual(getReviewFromStorage(i), new_review);
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem("activeIDS")), ids);
|
||||
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), (i+1));
|
||||
}
|
||||
}).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++){
|
||||
reviews = getReviewsFromStorage();
|
||||
|
||||
reviews.push(
|
||||
{
|
||||
"imgSrc": `sample src ${i}`,
|
||||
"imgAlt": `sample alt ${i}`,
|
||||
"mealName": `sample name ${i}`,
|
||||
"restaurant": `sample restaurant ${i}`,
|
||||
"rating": i,
|
||||
"tags": [`tag ${3*i}`, `tag ${3*i + 1}`, `tag ${3*i + 2}`]
|
||||
}
|
||||
);
|
||||
saveReviewsToStorage(reviews);
|
||||
assert.deepEqual(getReviewsFromStorage(), reviews);
|
||||
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}`]
|
||||
};
|
||||
new_review.reviewID = i;
|
||||
|
||||
reviews[i] = new_review;
|
||||
|
||||
updateReviewToStorage(i, new_review);
|
||||
|
||||
assert.deepEqual(getAllReviewsFromStorage(), reviews);
|
||||
assert.deepEqual(getReviewFromStorage(i), new_review);
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem("activeIDS")), ids);
|
||||
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 1000);
|
||||
}
|
||||
}).timeout(10000);
|
||||
}).timeout(5000);
|
||||
|
||||
it("test localStorage state during deleting 1000 reviews", () => {
|
||||
let reviews = getAllReviewsFromStorage();
|
||||
let ids = JSON.parse(localStorage.getItem("activeIDS"));
|
||||
|
||||
for(let i = 999; i >= 0; i--){
|
||||
deleteReviewFromStorage(i);
|
||||
ids.pop();
|
||||
reviews.pop();
|
||||
|
||||
assert.deepEqual(getAllReviewsFromStorage(), reviews);
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem("activeIDS")), ids);
|
||||
assert.strictEqual(JSON.parse(localStorage.getItem("nextID")), 1000);
|
||||
}
|
||||
}).timeout(5000);
|
||||
|
||||
after(() => {});
|
||||
});
|
||||
|
232
source/assets/scripts/main.e2e.test.js
Normal file
@@ -0,0 +1,232 @@
|
||||
import {strict as assert} from "node:assert";
|
||||
import {describe, it, before, after} from "mocha";
|
||||
import puppeteer from "puppeteer-core";
|
||||
import {exit} from "node:process";
|
||||
import {setReviewForm, checkCorrectness} from "./appTestHelpers.js";
|
||||
|
||||
describe("test App end to end", async () => {
|
||||
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
before(async () => {
|
||||
let root;
|
||||
try {
|
||||
root = process.getuid() == 0;
|
||||
}
|
||||
catch (error) {
|
||||
root = false;
|
||||
}
|
||||
|
||||
//browser = await puppeteer.launch({headless: false, slowMo: 250, args: root ? ['--no-sandbox'] : undefined});
|
||||
browser = await puppeteer.launch({args: root ? ["--no-sandbox"] : undefined});
|
||||
page = await browser.newPage();
|
||||
try{
|
||||
await page.goto("http://localhost:8080", {timeout: 2000});
|
||||
await console.log(`✔ connected to localhost webserver as ${root ? "root" : "user"}`);
|
||||
}
|
||||
catch (error) {
|
||||
await console.log("❌ failed to connect to localhost webserver on port 8080");
|
||||
await exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
describe("test simple properties", async () => {
|
||||
it("page should have correct title", async () => {
|
||||
assert.strictEqual(await page.title(), "Food Journal");
|
||||
});
|
||||
});
|
||||
|
||||
describe("test CRUD on simple inputs and default image", () => {
|
||||
|
||||
describe("test create 1 new review", async () => {
|
||||
it("create 1 new review", async () => {
|
||||
// Click the button to create a new review
|
||||
let create_btn = await page.$("#create-btn");
|
||||
await create_btn.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
// create a new review
|
||||
let review = {
|
||||
mealName: "sample name",
|
||||
comments: "sample comment",
|
||||
restaurant: "sample restaurant",
|
||||
tags: ["tag 0", "tag 1", "tag 2", "tag 3", "tag 4"],
|
||||
rating: 1
|
||||
};
|
||||
await setReviewForm(page, review);
|
||||
|
||||
// Click the save button to save updates
|
||||
let save_btn = await page.$("#save-btn");
|
||||
await save_btn.click();
|
||||
await page.waitForNavigation();
|
||||
});
|
||||
|
||||
it("check details page", async () => {
|
||||
// check the details page for correctness
|
||||
let expected = {
|
||||
imgSrc: "http://localhost:8080/assets/images/plate_with_cutlery.png",
|
||||
mealName: "sample name",
|
||||
comments: "sample comment",
|
||||
restaurant: "sample restaurant",
|
||||
tags: ["tag 0", "tag 1", "tag 2", "tag 3", "tag 4"],
|
||||
rating: "http://localhost:8080/assets/images/1-star.svg"
|
||||
};
|
||||
await checkCorrectness(page, "d", expected);
|
||||
});
|
||||
|
||||
it("check home page", async () => {
|
||||
// Click the button to return to the home page
|
||||
let home_btn = await page.$("#home-btn");
|
||||
home_btn.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
// Get the review card again and get its shadowRoot
|
||||
let review_card = await page.$("review-card");
|
||||
let shadowRoot = await review_card.getProperty("shadowRoot");
|
||||
|
||||
let expected = {
|
||||
imgSrc: "http://localhost:8080/assets/images/plate_with_cutlery.png",
|
||||
mealName: "sample name",
|
||||
comments: "sample comment",
|
||||
restaurant: "sample restaurant",
|
||||
tags: ["tag 0", "tag 1", "tag 2", "tag 3", "tag 4"],
|
||||
rating: "http://localhost:8080/assets/images/1-star.svg"
|
||||
};
|
||||
await checkCorrectness(shadowRoot, "a", expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("test read 1 review after refresh", async () => {
|
||||
it("refresh page", async () => {
|
||||
// Reload the page
|
||||
await page.reload({ waitUntil: ["networkidle0", "domcontentloaded"] });
|
||||
});
|
||||
|
||||
it("check details page", async () => {
|
||||
// click review card
|
||||
let review_card = await page.$("review-card");
|
||||
await review_card.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
// check the details page for correctness
|
||||
let expected = {
|
||||
imgSrc: "http://localhost:8080/assets/images/plate_with_cutlery.png",
|
||||
mealName: "sample name",
|
||||
comments: "sample comment",
|
||||
restaurant: "sample restaurant",
|
||||
tags: ["tag 0", "tag 1", "tag 2", "tag 3", "tag 4"],
|
||||
rating: "http://localhost:8080/assets/images/1-star.svg"
|
||||
};
|
||||
await checkCorrectness(page, "d", expected);
|
||||
});
|
||||
|
||||
it("check home page", async () => {
|
||||
// Click the button to return to the home page
|
||||
let home_btn = await page.$("#home-btn");
|
||||
home_btn.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
// Get the review card again and get its shadowRoot
|
||||
let review_card = await page.$("review-card");
|
||||
let shadowRoot = await review_card.getProperty("shadowRoot");
|
||||
|
||||
// check the details page for correctness
|
||||
let expected = {
|
||||
imgSrc: "http://localhost:8080/assets/images/plate_with_cutlery.png",
|
||||
mealName: "sample name",
|
||||
comments: "sample comment",
|
||||
restaurant: "sample restaurant",
|
||||
tags: ["tag 0", "tag 1", "tag 2", "tag 3", "tag 4"],
|
||||
rating: "http://localhost:8080/assets/images/1-star.svg"
|
||||
};
|
||||
await checkCorrectness(shadowRoot, "a", expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("test update 1 review", async () => {
|
||||
|
||||
it("update 1 review", async () => {
|
||||
|
||||
// Get the only review card and click it
|
||||
let review_card = await page.$("review-card");
|
||||
await review_card.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
// Click the button to show update form
|
||||
let update_btn = await page.$("#update-btn");
|
||||
await update_btn.click();
|
||||
|
||||
// create a new review
|
||||
let review = {
|
||||
mealName: "updated name",
|
||||
comments: "updated comment",
|
||||
restaurant: "updated restaurant",
|
||||
tags: ["tag -0", "tag -1", "tag -2", "tag -3", "tag -4", "tag -5"],
|
||||
rating: 5
|
||||
};
|
||||
await setReviewForm(page, review);
|
||||
|
||||
// Click the save button to save updates
|
||||
let save_btn = await page.$("#save-btn");
|
||||
await save_btn.click();
|
||||
await page.waitForNavigation();
|
||||
}).timeout(10000);
|
||||
|
||||
it("check details page", async () => {
|
||||
// check the details page for correctness
|
||||
let expected = {
|
||||
imgSrc: "http://localhost:8080/assets/images/plate_with_cutlery.png",
|
||||
mealName: "updated name",
|
||||
comments: "updated comment",
|
||||
restaurant: "updated restaurant",
|
||||
tags: ["tag -0", "tag -1", "tag -2", "tag -3", "tag -4", "tag -5"],
|
||||
rating: "http://localhost:8080/assets/images/5-star.svg"
|
||||
};
|
||||
await checkCorrectness(page, "d", expected);
|
||||
});
|
||||
|
||||
it("check home page", async () => {
|
||||
// Click the button to return to the home page
|
||||
let home_btn = await page.$("#home-btn");
|
||||
home_btn.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
// Get the review card again and get its shadowRoot
|
||||
let review_card = await page.$("review-card");
|
||||
let shadowRoot = await review_card.getProperty("shadowRoot");
|
||||
|
||||
// check the details page for correctness
|
||||
let expected = {
|
||||
imgSrc: "http://localhost:8080/assets/images/plate_with_cutlery.png",
|
||||
mealName: "updated name",
|
||||
comments: "updated comment",
|
||||
restaurant: "updated restaurant",
|
||||
tags: ["tag -0", "tag -1", "tag -2", "tag -3", "tag -4", "tag -5"],
|
||||
rating: "http://localhost:8080/assets/images/5-star.svg"
|
||||
};
|
||||
await checkCorrectness(shadowRoot, "a", expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("test delete 1 review", async () => {
|
||||
it("delete 1 review", async () => {
|
||||
// Get the only review card and click it
|
||||
let review_card = await page.$("review-card");
|
||||
await review_card.click();
|
||||
await page.waitForNavigation();
|
||||
|
||||
page.on("dialog", async dialog => {
|
||||
console.log(dialog.message());
|
||||
await dialog.accept();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await page.close();
|
||||
await browser.close();
|
||||
});
|
||||
});
|
@@ -1,28 +1,29 @@
|
||||
// main.js
|
||||
import {getReviewsFromStorage, saveReviewsToStorage} from "./localStorage.js";
|
||||
import {getAllReviewsFromStorage} 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 = getReviewsFromStorage();
|
||||
let reviews = getAllReviewsFromStorage();
|
||||
// Add each reviews to the <main> element
|
||||
addReviewsToDocument(reviews);
|
||||
// Add the event listeners to the form elements
|
||||
initFormHandler();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Array<Object>} reviews An array of reviews
|
||||
*/
|
||||
function addReviewsToDocument(reviews) {
|
||||
let mainEl = document.querySelector("main");
|
||||
let reviewBox = document.getElementById("review-container");
|
||||
reviews.forEach(review => {
|
||||
let newReview = document.createElement("review-card");
|
||||
newReview.data = review;
|
||||
//TODO: want to append it to whatever the box is in layout
|
||||
mainEl.append(newReview);
|
||||
reviewBox.append(newReview);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -33,87 +34,9 @@ function addReviewsToDocument(reviews) {
|
||||
*/
|
||||
function initFormHandler() {
|
||||
|
||||
/*
|
||||
//btn to create form (could be its own function?)
|
||||
let createBtn = document.getElementById("create");
|
||||
let createBtn = document.getElementById("create-btn");
|
||||
createBtn.addEventListener("click", function(){
|
||||
window.location.assign("./CreatePage.html");
|
||||
});*/
|
||||
|
||||
//accessing form components
|
||||
let tagContainer = document.getElementById("tag-container-form");
|
||||
let form = document.querySelector("form");
|
||||
|
||||
form.addEventListener("submit", function(){
|
||||
/*
|
||||
* User submits the form for their review.
|
||||
* We create reviewCard and put in storage
|
||||
*/
|
||||
let formData = new FormData(form);
|
||||
let reviewObject = {};
|
||||
for (let [key, value] of formData) {
|
||||
console.log(`${key}`);
|
||||
console.log(`${value}`);
|
||||
if (`${key}` !== "tag-form") {
|
||||
reviewObject[`${key}`] = `${value}`;
|
||||
}
|
||||
}
|
||||
reviewObject["tags"] = [];
|
||||
|
||||
let tags = document.querySelectorAll(".tag");
|
||||
for(let i = 0; i < tags.length; i ++) {
|
||||
reviewObject["tags"].push(tags[i].innerHTML);
|
||||
tagContainer.removeChild(tags[i]);
|
||||
}
|
||||
|
||||
|
||||
let newReview = document.createElement("review-card");
|
||||
newReview.data = reviewObject;
|
||||
|
||||
//TODO: want to append it to whatever the box is in layout
|
||||
let mainEl = document.querySelector("main");
|
||||
mainEl.append(newReview);
|
||||
|
||||
let storedReviews = getReviewsFromStorage();
|
||||
storedReviews.push(reviewObject);
|
||||
saveReviewsToStorage(storedReviews);
|
||||
document.getElementById("new-food-entry").reset();
|
||||
});
|
||||
|
||||
// DEV-MODE: for testing purposes
|
||||
let clearBtn = document.querySelector(".danger");
|
||||
clearBtn.addEventListener("click", function() {
|
||||
localStorage.clear();
|
||||
let mainEl = document.querySelector("main");
|
||||
while (mainEl.firstChild) {
|
||||
mainEl.removeChild(mainEl.firstChild);
|
||||
}
|
||||
let deleteTags = document.querySelectorAll(".tag");
|
||||
for(let i = 0; i < deleteTags.length; i ++) {
|
||||
tagContainer.removeChild(deleteTags[i]);
|
||||
}
|
||||
|
||||
//clears reviews AS WELL as resets form
|
||||
document.getElementById("new-food-entry").reset();
|
||||
|
||||
|
||||
});
|
||||
|
||||
let tagAddBtn = document.getElementById("tagAdd");
|
||||
tagAddBtn.addEventListener("click", ()=> {
|
||||
let tagField = document.getElementById("tag-form");
|
||||
if (tagField.value.length > 0) {
|
||||
let tagLabel = document.createElement("label");
|
||||
tagLabel.innerHTML = tagField.value;
|
||||
tagLabel.setAttribute("class","tag");
|
||||
tagLabel.addEventListener("click",()=> {
|
||||
tagContainer.removeChild(tagLabel);
|
||||
});
|
||||
|
||||
tagContainer.append(tagLabel);
|
||||
tagField.value = "";
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
@@ -1,79 +1,57 @@
|
||||
|
||||
<!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>
|
||||
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
<!--Add Favicon-->
|
||||
<link rel="icon" type="image/x-icon" href="./assets/images/favicon.ico">
|
||||
|
||||
<!-- Recipe Card Custom Element -->
|
||||
<script src="assets/scripts/ReviewCard.js" type="module"></script>
|
||||
|
||||
<!-- 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/ReviewCard.css" />
|
||||
<script src="assets/scripts/main.js" type="module"></script>
|
||||
<!-- 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>
|
||||
<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" onclick="window.location.assign('./CreatePage.html')"/>
|
||||
<h2 id="recent-reviews-text"> Recent Reviews </h2>
|
||||
<img src ="./assets/images/Grouppink.png" style="opacity:0;"/>
|
||||
|
||||
</div>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<!-- Add Food Entries Here -->
|
||||
</main>
|
||||
<!--<button type="button" id="create">CREATE</button>-->
|
||||
<!----> <form id="new-food-entry">
|
||||
<fieldset>
|
||||
<legend>Pic:</legend>
|
||||
<label for="mealImage">
|
||||
Source:
|
||||
<input type="text" id="mealImg" name="mealImg">
|
||||
</label>
|
||||
<label for="image-alt">
|
||||
Alt Text:
|
||||
<input type="text" id="imgAlt" name="imgAlt">
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
<legend> Meal: </legend>
|
||||
<label for="Meal: ">Meal:
|
||||
<input type="text" id="mealName" name="mealName" required>
|
||||
</label>
|
||||
<label for="comments">Comments:
|
||||
<br>
|
||||
<textarea name="comments" id="comments"></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="rating">
|
||||
<legend> Rating: </legend>
|
||||
<input type="radio" id="s5" name="rating" value="5"/> <label for="s5"> 5 stars </label>
|
||||
<input type="radio" id="s4" name="rating" value="4"/> <label for="s4"> 4 stars </label>
|
||||
<input type="radio" id="s3" name="rating" value="3"/> <label for="s3"> 3 stars </label>
|
||||
<input type="radio" id="s2" name="rating" value="2"/> <label for="s2"> 2 stars </label>
|
||||
<input type="radio" id="s1" name="rating" value="1"/> <label for="s1"> 1 star </label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Other Info:</legend>
|
||||
<label for="restaurant">
|
||||
Restaurant:
|
||||
<input type="text" id="restaurant" name="restaurant" required>
|
||||
</label>
|
||||
<label for="tag-form">
|
||||
Tags:
|
||||
<input type="text" id="tag-form" name="tag-form">
|
||||
<div class='tag-container' id="tag-container-form">
|
||||
|
||||
</div>
|
||||
<button type="button" id="tagAdd">Add Tag</button>
|
||||
</label>
|
||||
|
||||
</fieldset>
|
||||
<button type="submit" value="Submit">Add Review</button>
|
||||
<button type="button" class="danger">Clear Review Journal</button>
|
||||
</form> <!---->
|
||||
</body>
|
||||
<div class="review-container" id="review-container"></div>
|
||||
</div>
|
||||
<div style="width: 20%;">
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,16 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<script src="assets/scripts/reviewpage.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Current Review:</h1>
|
||||
<main>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@@ -1,19 +0,0 @@
|
||||
[
|
||||
{
|
||||
"imgSrc": "https://cdn.vox-cdn.com/thumbor/Cj5J-5WqSCjlC2tWCOXEB536CJY=/0x0:1810x1182/1200x800/filters:focal(761x447:1049x735)/cdn.vox-cdn.com/uploads/chorus_image/image/69422966/Tacos_Lined_Up.0.png",
|
||||
"imgAlt": "tacos pic",
|
||||
"mealName": "Birria Tacos",
|
||||
"restaurant": "Mike's Red Tacos",
|
||||
"rating": 5,
|
||||
"tags": ["delicious", "#worthit","omg"]
|
||||
},
|
||||
{
|
||||
"imgSrc": "https://www.redwormcomposting.com/images/worm-burrito.JPG",
|
||||
"imgAlt": "wolftown pic",
|
||||
"mealName": "Carnitas Burrito",
|
||||
"restaurant": "Wolftown UCSD",
|
||||
"rating": 0,
|
||||
"tags": ["gross", "why","no"]
|
||||
|
||||
}
|
||||
]
|
BIN
source/static/CoveredByYourGrace-Regular.ttf
Normal file
@@ -1,83 +1,30 @@
|
||||
/* CreatePage.css */
|
||||
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
@font-face {
|
||||
font-family: testFont;
|
||||
src: url("CoveredByYourGrace-Regular.ttf");
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #f8f3f1;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 2px solid rgb(214 214 214);
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: max-content;
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
form button {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
.top-bar > img {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
padding-left: 2.5%;
|
||||
padding-right: 2.5%;
|
||||
}
|
||||
|
||||
label[for="ingredients"] p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label[for="numRatings"] {
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
label[for^="rating"] {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
label:not([for^="rating"]) {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
main {
|
||||
column-gap: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
max-width: 660px;
|
||||
row-gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: grey;
|
||||
border-radius: 7px;
|
||||
color: white;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.tag::before {
|
||||
display: inline-block;
|
||||
content: "x";
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-right: 4px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag:hover::before {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: rgb(254 171 171);
|
||||
border-color: red;
|
||||
.top-bar > h1 {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: #516754;
|
||||
font-size: 6rem;
|
||||
font-family: testFont, sans-serif;
|
||||
}
|
||||
|
153
source/static/Form.css
Normal file
@@ -0,0 +1,153 @@
|
||||
@font-face {
|
||||
font-family: testFont;
|
||||
src: url("CoveredByYourGrace-Regular.ttf");
|
||||
}
|
||||
|
||||
.journal-form h1 {
|
||||
font-family: testFont, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.journal-form {
|
||||
font-size: 120%;
|
||||
font-family: "Century Gothic", sans-serif;
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
color: #516754;
|
||||
border: 2px solid rgb(31 41 32);
|
||||
border-radius: 8px;
|
||||
background-color: #f7dfd5;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #f7dfd5;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgb(0 0 0);
|
||||
}
|
||||
|
||||
input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-bottom: 1px solid rgb(0 0 0);
|
||||
}
|
||||
|
||||
.rating {
|
||||
display: flex;
|
||||
flex-flow: nowrap row-reverse;
|
||||
}
|
||||
|
||||
.hidden,
|
||||
.rating:not(:checked) > input { /* Hide radio circles while star rating */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Unchecked stars */
|
||||
.rating:not(:checked) > label {
|
||||
/* Make stars line up sideways and not vertically */
|
||||
float: right;
|
||||
|
||||
/* Hide label text */
|
||||
width: 1em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
/* Star default color and size */
|
||||
font-size: 200%;
|
||||
line-height: 1.2;
|
||||
color: #b3b3cc;
|
||||
}
|
||||
|
||||
.rating > label:active {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.rating:not(:checked) > label::before {
|
||||
content: "★";
|
||||
}
|
||||
|
||||
/* Checked star color */
|
||||
.rating > input:checked ~ label {
|
||||
color: #ffbf00;
|
||||
}
|
||||
|
||||
.rating:not(:checked) > label:hover,
|
||||
.rating:not(:checked) > label:hover ~ label {
|
||||
color: orangered;
|
||||
}
|
||||
|
||||
.rating > input:checked + label:hover,
|
||||
.rating > input:checked ~ label:hover,
|
||||
.rating > input:checked + label:hover ~ label,
|
||||
.rating > input:checked ~ label:hover ~ label,
|
||||
.rating > label:hover ~ input:checked ~ label {
|
||||
color: orangered;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
display: flex;
|
||||
flex-flow: wrap row;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: grey;
|
||||
border-radius: 7px;
|
||||
color: white;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.tag::before {
|
||||
display: inline-block;
|
||||
content: "x";
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-right: 4px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag:hover::before {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#tag-add-btn {
|
||||
background-color: #94da97; /* Green */
|
||||
border: round;
|
||||
color: rgb(206 83 179);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
border-radius: 10%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#tag-add-btn:hover {
|
||||
background-color: rgb(206 83 179); /* Green */
|
||||
border: round;
|
||||
color: #94da97;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
border-radius: 10%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tag-container * {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
93
source/static/OFL.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010, Kimberly Geswein (kimberlygeswein.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
@@ -1,83 +0,0 @@
|
||||
/* main.css */
|
||||
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 2px solid rgb(214 214 214);
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
form button {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
label[for="ingredients"] p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label[for="numRatings"] {
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
label[for^="rating"] {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
label:not([for^="rating"]) {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
main {
|
||||
column-gap: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
max-width: 660px;
|
||||
row-gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: grey;
|
||||
border-radius: 7px;
|
||||
color: white;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.tag::before {
|
||||
display: inline-block;
|
||||
content: "x";
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-right: 4px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag:hover::before {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: rgb(254 171 171);
|
||||
border-color: red;
|
||||
}
|
45
source/static/ReviewDetails.css
Normal file
@@ -0,0 +1,45 @@
|
||||
/* ReviewDetails.css */
|
||||
|
||||
@font-face {
|
||||
font-family: testFont;
|
||||
src: url("CoveredByYourGrace-Regular.ttf");
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f8f3f1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.top-bar > img {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
padding-left: 2.5%;
|
||||
padding-right: 2.5%;
|
||||
}
|
||||
|
||||
.top-bar > h1 {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
/* color: #e4c3d2; */
|
||||
color: #516754;
|
||||
font-size: 6rem;
|
||||
font-family: testFont, sans-serif;
|
||||
}
|
||||
|
||||
.d-tag {
|
||||
background-color: grey;
|
||||
border-radius: 7px;
|
||||
color: white;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
margin: 10px;
|
||||
}
|
93
source/static/homepage.css
Normal file
@@ -0,0 +1,93 @@
|
||||
/* homepage.css */
|
||||
|
||||
@font-face {
|
||||
font-family: testFont;
|
||||
src: url("CoveredByYourGrace-Regular.ttf");
|
||||
}
|
||||
|
||||
/* Color */
|
||||
body {
|
||||
/* background-color: #97a5bd */
|
||||
|
||||
/* background-color: #E3E3EC; */
|
||||
background-color: #f8f3f1;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.top-bar > img {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
padding-left: 2.5%;
|
||||
padding-right: 2.5%;
|
||||
}
|
||||
|
||||
.top-bar > h1 {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
/* color: #e4c3d2; */
|
||||
|
||||
/* color: rgb(145, 124, 175); */
|
||||
color: #516754;
|
||||
font-size: 6rem;
|
||||
font-family: testFont, sans-serif;
|
||||
}
|
||||
|
||||
.body-container {
|
||||
display: flex;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.center-display {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
#recent-reviews-text {
|
||||
text-align: center;
|
||||
font-size: 4rem;
|
||||
color: #516754;
|
||||
font-family: testFont, sans-serif;
|
||||
}
|
||||
|
||||
img#create-btn {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
padding-left: 2.5%;
|
||||
padding-right: 2.5%;
|
||||
}
|
||||
|
||||
.review-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.review-container > div {
|
||||
background-color: #f1f1f1;
|
||||
text-align: center;
|
||||
}
|
@@ -1,158 +0,0 @@
|
||||
/* This is a generic CSS file that sets preliminary rules for content that should be the same across pages */
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
abbr,
|
||||
address,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
samp,
|
||||
small,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
var,
|
||||
b,
|
||||
i,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote::before,
|
||||
blockquote::after,
|
||||
q::before,
|
||||
q::after {
|
||||
content: "";
|
||||
content: none;
|
||||
}
|
||||
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
img,
|
||||
fieldset,
|
||||
object {
|
||||
border: none;
|
||||
}
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button,
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
form {
|
||||
border: solid;
|
||||
}
|
19
specs/adrs/111422-csslinting-stylelint.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Use Stylelint for CSS linting framework
|
||||
|
||||
- Status: accept
|
||||
- Deciders: Arthur Lu, Marc Reta
|
||||
- Date: 11 / 14 / 22
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Need linting to work with multiple style standards
|
||||
- Need linting to be fast and informative
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Stylelint
|
||||
- Prettier
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen Option: Stylelint for its easy installation and unopinionated.
|
19
specs/adrs/111422-htmllinting-htmlhint.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Use HTMLhint for HTML linting framework
|
||||
|
||||
- Status: accept
|
||||
- Deciders: Arthur Lu, Marc Reta
|
||||
- Date: 11 / 14 / 22
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Need linting to work with multiple style standards
|
||||
- Need linting to be fast and informative
|
||||
|
||||
## Considered Options
|
||||
|
||||
- HTMLhint
|
||||
- HTML-validate
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen Option: HTMLhint for its low configuration complexity.
|
19
specs/adrs/111622-e2etesting-puppeteer.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Use puppeteer for JS unit testing framework
|
||||
|
||||
- Status: accept
|
||||
- Deciders: Arthur Lu, Marc Reta
|
||||
- Date: 11 / 16 / 22
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Need end to end testing framework which runs headlessly and quickly
|
||||
- Framework should integrate well with Mocha, the existing unit testing framework
|
||||
- Framework should be easy to implement end to end tests with
|
||||
|
||||
## Considered Options
|
||||
- puppeteer
|
||||
- selenium-webdriver
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen Option: Puppeteer for its ease of use with mocha.
|