Skip to content

Commit 1670231

Browse files
committed
add flashcard app
1 parent 207b827 commit 1670231

File tree

7 files changed

+382
-0
lines changed

7 files changed

+382
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Welcome to the Mini Projects Showcase! This repository contains a collection of
1313
- Country Info
1414
- Currency Converter
1515
- Dynamic Menu
16+
- Flashcard App
1617
- Gradient Generator
1718
- Image Search Engine
1819
- Income Expense Tracker

data.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,8 @@ const projects = [
115115
name: "Income Expense Tracker",
116116
link: "income-expense-tracker/index.html",
117117
},
118+
{
119+
name: "Flashcard App",
120+
link: "flashcard-app/index.html",
121+
},
118122
];

flashcard-app/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Flashcard Web App
2+
3+
A simple flashcard web application that allows users to create, edit, view, and delete flashcards categorized into different topics. This app saves flashcards locally using localStorage so users can access their flashcards even after refreshing the page.
4+
5+
## Features
6+
7+
- **Create New Flashcards**: Users can input a question, an answer, and select a category to save a new flashcard.
8+
- **Edit Flashcards**: Users can modify existing flashcards.
9+
- **Delete Flashcards**: Users can delete flashcards when no longer needed.
10+
- **Toggle Answer Visibility**: Users can toggle between showing or hiding the answer of a flashcard.
11+
- **Categorization**: Flashcards are categorized into predefined categories (e.g., General Knowledge, Science, History).
12+
- **Data Persistence**: Flashcards are saved to localStorage, allowing them to persist even after page reloads.
13+
14+
## Tools & Technologies Used
15+
16+
- **HTML**: Structure and content of the application.
17+
- **CSS**: Styling and layout of the app.
18+
- **JavaScript**: Application logic for handling flashcards (creating, editing, saving to localStorage, etc.).
19+
- **Bootstrap**: Used for layout and responsive design.
20+
- **Remix Icons**: For icons like delete, edit, etc.
21+
22+
## Screenshot
23+
24+
![Screenshot](flashcard_ui.png)

flashcard-app/app.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
const addFlashcardBtn = document.querySelector(".add-flashcard"),
2+
addFlashcardContainer = document.querySelector(".add-flashcard-container"),
3+
closeIcon = document.querySelector(".ri-close-large-line"),
4+
saveFlashCardBtn = document.querySelector(".add-flashcard-container button"),
5+
question = document.querySelector("#question"),
6+
answer = document.querySelector("#answer"),
7+
flashCardWrapper = document.querySelector(".flashcard-wrapper"),
8+
categorySelect = document.querySelector("#category-select");
9+
10+
let editable = false;
11+
let currentFlashCard = null;
12+
13+
// Show flashcard container
14+
const showFlashcardContainer = (
15+
edit = false,
16+
questionVal = "",
17+
answerVal = "",
18+
categoryVal = "GK"
19+
) => {
20+
addFlashcardContainer.querySelector("h4").textContent = edit
21+
? "Edit Flashcard"
22+
: "Save Flashcard";
23+
saveFlashCardBtn.textContent = edit ? "Apply Changes" : "Save";
24+
editable = edit;
25+
26+
// Reset fields if adding a new flashcard
27+
question.value = questionVal;
28+
answer.value = answerVal;
29+
categorySelect.value = categoryVal;
30+
31+
addFlashcardContainer.style.display = "flex";
32+
document.body.style.overflowY = "hidden";
33+
question.focus();
34+
};
35+
36+
// Hide the flashcard container
37+
const hideFlashcardContainer = () => {
38+
addFlashcardContainer.style.display = "none";
39+
document.body.style.overflowY = "auto";
40+
};
41+
42+
// Flashcard category styles
43+
const flashcardStyles = (category) =>
44+
({
45+
GK: "gk",
46+
Science: "science",
47+
History: "history",
48+
}[category] || "undefined");
49+
50+
// Flashcard HTML structure generator
51+
const flashCardGenerator = (questionText, answerText, categoryText) => {
52+
const bgClass = flashcardStyles(categoryText);
53+
return `<div
54+
class="d-flex flex-column gap-1 flashcard-box ${bgClass} p-3 rounded-2"
55+
>
56+
<h5 class="fw-medium que-text m-0 pb-2">${questionText}</h5>
57+
<p class="m-0 pb-1 fw-medium text-secondary-emphasis ans-text">${answerText}</p>
58+
<div class="d-flex flex-column mt-auto">
59+
<button class="btn btn-dark show-hide-btn">Show</button>
60+
<div class="d-flex justify-content-between align-items-center mt-2">
61+
<p class="category-text">${categoryText}</p>
62+
<div class="d-flex gap-2">
63+
<i class="ri-edit-box-line text-primary"></i>
64+
<i class="ri-delete-bin-line text-danger"></i>
65+
</div>
66+
</div>
67+
</div>
68+
</div>`;
69+
};
70+
71+
// Create a new flashcard
72+
const createFlashCard = () => {
73+
flashCardWrapper.insertAdjacentHTML(
74+
"beforeend",
75+
flashCardGenerator(question.value, answer.value, categorySelect.value)
76+
);
77+
saveDataToLocalStorage();
78+
};
79+
80+
// Edit an existing flashcard
81+
const editFlashCard = () => {
82+
if (currentFlashCard) {
83+
const questionText = currentFlashCard.querySelector(".que-text");
84+
const answerText = currentFlashCard.querySelector(".ans-text");
85+
const categoryText = currentFlashCard.querySelector(".category-text");
86+
87+
questionText.textContent = question.value;
88+
answerText.textContent = answer.value;
89+
categoryText.textContent = categorySelect.value;
90+
91+
const bgClass = flashcardStyles(categorySelect.value);
92+
if (!currentFlashCard.classList.contains(bgClass)) {
93+
currentFlashCard.classList.remove(
94+
"gk",
95+
"science",
96+
"history",
97+
"undefined"
98+
);
99+
currentFlashCard.classList.add(bgClass);
100+
}
101+
}
102+
saveDataToLocalStorage();
103+
hideFlashcardContainer();
104+
};
105+
106+
// Event listeners
107+
addFlashcardBtn.addEventListener("click", () => showFlashcardContainer(false));
108+
109+
closeIcon.addEventListener("click", hideFlashcardContainer);
110+
111+
saveFlashCardBtn.addEventListener("click", () => {
112+
if (!question.value || !answer.value) {
113+
return alert("All fields is required");
114+
}
115+
116+
editable ? editFlashCard() : createFlashCard();
117+
hideFlashcardContainer();
118+
});
119+
120+
flashCardWrapper.addEventListener("click", (e) => {
121+
const currentBox = e.target.closest(".flashcard-box");
122+
const currentQueText = currentBox?.querySelector(".que-text");
123+
const currentAnsText = currentBox.querySelector(".ans-text");
124+
const currentCategoryText = currentBox.querySelector(".category-text");
125+
126+
if (e.target.classList.contains("show-hide-btn")) {
127+
currentAnsText.classList.toggle("show");
128+
e.target.textContent = currentAnsText.classList.contains("show")
129+
? "Hide"
130+
: "Show";
131+
}
132+
if (e.target.classList.contains("ri-delete-bin-line")) {
133+
currentBox.remove();
134+
saveDataToLocalStorage();
135+
}
136+
if (e.target.classList.contains("ri-edit-box-line")) {
137+
currentFlashCard = currentBox;
138+
showFlashcardContainer(
139+
true,
140+
currentQueText.textContent,
141+
currentAnsText.textContent,
142+
currentCategoryText.textContent
143+
);
144+
}
145+
});
146+
147+
// Save flashcards to localStorage
148+
const saveDataToLocalStorage = () => {
149+
const flashcards = Array.from(
150+
flashCardWrapper.querySelectorAll(".flashcard-box")
151+
).map((box) => ({
152+
queText: box.querySelector(".que-text").textContent,
153+
ansText: box.querySelector(".ans-text").textContent,
154+
categoryText: box.querySelector(".category-text").textContent,
155+
}));
156+
localStorage.setItem("flashcards", JSON.stringify(flashcards));
157+
};
158+
159+
// Load flashcards from localStorage
160+
const loadDataFromLocalStorage = () => {
161+
const storedFlashCards = JSON.parse(localStorage.getItem("flashcards")) || [];
162+
storedFlashCards.forEach(({ queText, ansText, categoryText }) => {
163+
flashCardWrapper.insertAdjacentHTML(
164+
"beforeend",
165+
flashCardGenerator(queText, ansText, categoryText)
166+
);
167+
});
168+
};
169+
170+
// Load flashcards on page loads
171+
window.addEventListener("DOMContentLoaded", loadDataFromLocalStorage);

flashcard-app/flashcard_ui.png

454 KB
Loading

flashcard-app/index.html

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Flashcard App</title>
7+
<link
8+
rel="stylesheet"
9+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
10+
/>
11+
<link
12+
href="https://cdn.jsdelivr.net/npm/remixicon@4.5.0/fonts/remixicon.css"
13+
rel="stylesheet"
14+
/>
15+
<link rel="stylesheet" href="style.css" />
16+
</head>
17+
<body>
18+
<div class="container">
19+
<div class="row">
20+
<div class="col d-flex flex-column gap-4 py-3">
21+
<button class="add-flashcard btn btn-primary ms-auto">
22+
Add Flashcard
23+
</button>
24+
<div class="flashcard-wrapper"></div>
25+
</div>
26+
</div>
27+
</div>
28+
<div class="add-flashcard-container">
29+
<div class="container">
30+
<div class="row justify-content-center">
31+
<div class="col-11 col-lg-8 col-xl-6 bg-white rounded-3 p-3">
32+
<h4 class="text-center fw-bold pb-2"></h4>
33+
<div class="d-flex flex-column gap-3">
34+
<div class="d-flex flex-column gap-1">
35+
<label for="question" class="fw-semibold">Question:</label>
36+
<input
37+
id="question"
38+
class="form-control"
39+
placeholder="Type the question here..."
40+
required
41+
></input>
42+
</div>
43+
<div class="d-flex flex-column gap-1">
44+
<label for="answer" class="fw-semibold">Answer:</label>
45+
<input
46+
id="answer"
47+
class="form-control"
48+
placeholder="Type the answer here..."
49+
required
50+
></input>
51+
</div>
52+
<div class="d-flex flex-column gap-1">
53+
<label class="fw-semibold">Category:</label>
54+
<select id="category-select" class="form-select">
55+
<option value="GK">GK</option>
56+
<option value="Science">Science</option>
57+
<option value="History">History</option>
58+
</select>
59+
</div>
60+
<button class="btn btn-success"></button>
61+
</div>
62+
</div>
63+
</div>
64+
</div>
65+
<i class="ri-close-large-line"></i>
66+
</div>
67+
</body>
68+
<script src="app.js"></script>
69+
</html>
70+

flashcard-app/style.css

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@100..900&display=swap");
2+
3+
* {
4+
margin: 0;
5+
padding: 0;
6+
box-sizing: border-box;
7+
font-family: "Montserrat", sans-serif;
8+
}
9+
10+
html,
11+
body {
12+
width: 100%;
13+
height: 100%;
14+
}
15+
16+
body {
17+
position: relative;
18+
}
19+
20+
.flashcard-wrapper {
21+
display: grid;
22+
grid-template-columns: repeat(3, 1fr);
23+
gap: 20px;
24+
}
25+
26+
.flashcard-box {
27+
box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px,
28+
rgba(0, 0, 0, 0.06) 0px 0px 0px 1px;
29+
}
30+
31+
.ans-text {
32+
display: none;
33+
}
34+
35+
.ans-text.show {
36+
display: block;
37+
}
38+
39+
.category-text {
40+
font-style: italic;
41+
font-size: 15px;
42+
margin: 0;
43+
}
44+
45+
.flashcard-box {
46+
transition: background-color 0.5s ease;
47+
}
48+
49+
.flashcard-box.gk {
50+
background-color: #e7fdb3;
51+
}
52+
53+
.flashcard-box.science {
54+
background-color: #c8edc8;
55+
}
56+
57+
.flashcard-box.history {
58+
background-color: #f5f5dc;
59+
}
60+
61+
.flashcard-box i {
62+
font-size: 21px;
63+
cursor: pointer;
64+
}
65+
66+
.add-flashcard-container {
67+
position: fixed;
68+
left: 0;
69+
top: 0;
70+
width: 100vw;
71+
height: 100vh;
72+
background: rgba(0, 0, 0, 0.6);
73+
display: none;
74+
justify-content: center;
75+
align-items: center;
76+
}
77+
78+
.form-control,
79+
.form-select {
80+
border-color: #807979;
81+
}
82+
83+
:is(.form-control, .form-select):focus {
84+
box-shadow: unset;
85+
border-color: #2a2a2a;
86+
}
87+
88+
.ri-close-large-line {
89+
position: absolute;
90+
right: 15px;
91+
top: 10px;
92+
color: #fff;
93+
font-size: 25px;
94+
cursor: pointer;
95+
transition: transform 0.5s ease;
96+
}
97+
98+
.ri-close-large-line:hover {
99+
transform: rotate(180deg);
100+
}
101+
102+
@media (max-width: 992px) {
103+
.flashcard-wrapper {
104+
grid-template-columns: repeat(2, 1fr);
105+
}
106+
}
107+
108+
@media (max-width: 576px) {
109+
.flashcard-wrapper {
110+
grid-template-columns: 1fr;
111+
}
112+
}

0 commit comments

Comments
 (0)