lab 02
This commit is contained in:
6 files changed
+460
No files matched your search
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
Chat Gipity glossed over some CSS for me
|
||||||
|
so I dont have to claw out my eyes looking
|
||||||
|
at my own hot garbage styling
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
color: #172033;
|
||||||
|
background-color: #f5f7fb;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(92%, 48rem);
|
||||||
|
margin: 3rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(1.8rem, 5vw, 2.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page {
|
||||||
|
padding: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #d8deea;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 0.75rem 2rem rgb(23 32 51 / 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-name {
|
||||||
|
margin: 0.75rem 0 2rem;
|
||||||
|
color: #536078;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-link,
|
||||||
|
.back-link {
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
background-color: #315fc4;
|
||||||
|
padding: 0.75rem 1.2rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-page {
|
||||||
|
min-height: 36rem;
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #b9c4d8;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
min-height: 1.2rem;
|
||||||
|
margin: 0.4rem 0 0;
|
||||||
|
color: #536078;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note textarea {
|
||||||
|
min-height: 7rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
color: #172033;
|
||||||
|
background-color: #eef3ff;
|
||||||
|
border: 1px solid #8398c2;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
font: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
color: white;
|
||||||
|
background-color: #c43e45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button-control {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: white;
|
||||||
|
background-color: #2f7a42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
margin-top: 2rem;
|
||||||
|
background-color: #536078;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-note {
|
||||||
|
min-height: 5rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
color: white;
|
||||||
|
background-color: #315fc4;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-message {
|
||||||
|
color: #536078;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 36rem) {
|
||||||
|
main {
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page,
|
||||||
|
.notes-page {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header,
|
||||||
|
.note {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="landing-page">
|
||||||
|
<h1 id="page-title"></h1>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<nav class="page-links" aria-label="Lab pages">
|
||||||
|
<a id="writer-link" class="button-link" href="./writer.html"></a>
|
||||||
|
<a id="reader-link" class="button-link" href="./reader.html"></a>
|
||||||
|
</nav>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type="module" src="./js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
import { STRINGS } from "../lang/messages/en/user.js";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "comp4537-lab2-notes";
|
||||||
|
const UPDATE_INTERVAL = 2000;
|
||||||
|
|
||||||
|
class NoteButton {
|
||||||
|
constructor(label, className, clickHandler) {
|
||||||
|
this.element = document.createElement("button");
|
||||||
|
this.element.type = "button";
|
||||||
|
this.element.classList.add(className);
|
||||||
|
this.element.textContent = label;
|
||||||
|
this.element.addEventListener("click", clickHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
getElement() {
|
||||||
|
return this.element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Note {
|
||||||
|
constructor(id, content, removeHandler) {
|
||||||
|
this.id = id;
|
||||||
|
this.container = document.createElement("article");
|
||||||
|
this.textArea = document.createElement("textarea");
|
||||||
|
this.removeButton = new NoteButton(
|
||||||
|
STRINGS.REMOVE_BUTTON,
|
||||||
|
"remove-button",
|
||||||
|
() => removeHandler(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.container.classList.add("note");
|
||||||
|
this.textArea.value = content;
|
||||||
|
this.textArea.placeholder = STRINGS.NOTE_PLACEHOLDER;
|
||||||
|
this.textArea.setAttribute("aria-label", STRINGS.NOTE_PLACEHOLDER);
|
||||||
|
|
||||||
|
this.container.append(
|
||||||
|
this.textArea,
|
||||||
|
this.removeButton.getElement()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getElement() {
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
content: this.textArea.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.container.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteStorage {
|
||||||
|
load() {
|
||||||
|
const storedNotes = localStorage.getItem(STORAGE_KEY);
|
||||||
|
|
||||||
|
if (storedNotes === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const notes = JSON.parse(storedNotes);
|
||||||
|
return Array.isArray(notes) ? notes : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save(notes) {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Writer {
|
||||||
|
constructor(storage) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.notes = [];
|
||||||
|
this.notesElement = document.getElementById("notes");
|
||||||
|
this.statusElement = document.getElementById("status");
|
||||||
|
this.addButtonElement = document.getElementById("add-button");
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
document.title = STRINGS.WRITER_TITLE;
|
||||||
|
document.getElementById("page-title").textContent = STRINGS.WRITER_TITLE;
|
||||||
|
document.getElementById("back-link").textContent = STRINGS.BACK_LINK;
|
||||||
|
|
||||||
|
const addButton = new NoteButton(
|
||||||
|
STRINGS.ADD_BUTTON,
|
||||||
|
"add-button-control",
|
||||||
|
() => this.addNote()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addButtonElement.replaceWith(addButton.getElement());
|
||||||
|
|
||||||
|
for (const noteData of this.storage.load()) {
|
||||||
|
this.createNote(noteData.id, noteData.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setInterval(() => this.saveNotes(), UPDATE_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
addNote() {
|
||||||
|
const id = `${Date.now()}-${Math.random()}`;
|
||||||
|
this.createNote(id, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
createNote(id, content) {
|
||||||
|
const note = new Note(id, content, (selectedNote) => {
|
||||||
|
this.removeNote(selectedNote);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.notes.push(note);
|
||||||
|
this.notesElement.appendChild(note.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNote(note) {
|
||||||
|
this.notes = this.notes.filter((currentNote) => currentNote !== note);
|
||||||
|
note.remove();
|
||||||
|
this.saveNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveNotes() {
|
||||||
|
const noteData = this.notes.map((note) => note.getData());
|
||||||
|
this.storage.save(noteData);
|
||||||
|
this.statusElement.textContent = STRINGS.STORED_AT + this.getCurrentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTime() {
|
||||||
|
return new Date().toLocaleTimeString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Reader {
|
||||||
|
constructor(storage) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.notesElement = document.getElementById("notes");
|
||||||
|
this.statusElement = document.getElementById("status");
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
document.title = STRINGS.READER_TITLE;
|
||||||
|
document.getElementById("page-title").textContent = STRINGS.READER_TITLE;
|
||||||
|
document.getElementById("back-link").textContent = STRINGS.BACK_LINK;
|
||||||
|
|
||||||
|
this.updateNotes();
|
||||||
|
window.setInterval(() => this.updateNotes(), UPDATE_INTERVAL);
|
||||||
|
window.addEventListener("storage", (event) => {
|
||||||
|
if (event.key === STORAGE_KEY) {
|
||||||
|
this.updateNotes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNotes() {
|
||||||
|
const notes = this.storage.load();
|
||||||
|
this.notesElement.replaceChildren();
|
||||||
|
|
||||||
|
if (notes.length === 0) {
|
||||||
|
const message = document.createElement("p");
|
||||||
|
message.classList.add("empty-message");
|
||||||
|
message.textContent = STRINGS.EMPTY_NOTES;
|
||||||
|
this.notesElement.appendChild(message);
|
||||||
|
} else {
|
||||||
|
for (const note of notes) {
|
||||||
|
const noteElement = document.createElement("p");
|
||||||
|
noteElement.classList.add("reader-note");
|
||||||
|
noteElement.textContent = note.content;
|
||||||
|
this.notesElement.appendChild(noteElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.statusElement.textContent = STRINGS.UPDATED_AT + this.getCurrentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTime() {
|
||||||
|
return new Date().toLocaleTimeString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LandingPage {
|
||||||
|
init() {
|
||||||
|
document.title = STRINGS.INDEX_TITLE;
|
||||||
|
document.getElementById("page-title").textContent = STRINGS.INDEX_TITLE;
|
||||||
|
document.getElementById("writer-link").textContent = STRINGS.WRITER_LINK;
|
||||||
|
document.getElementById("reader-link").textContent = STRINGS.READER_LINK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage = new NoteStorage();
|
||||||
|
const pageName = window.location.pathname.split("/").pop() || "index.html";
|
||||||
|
|
||||||
|
if (pageName === "writer.html") {
|
||||||
|
new Writer(storage).init();
|
||||||
|
} else if (pageName === "reader.html") {
|
||||||
|
new Reader(storage).init();
|
||||||
|
} else {
|
||||||
|
new LandingPage().init();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const STRINGS = {
|
||||||
|
INDEX_TITLE: "Lab 1: JSON, Object Constructor, localStorage",
|
||||||
|
WRITER_LINK: "Write Notes",
|
||||||
|
READER_LINK: "Read Notes",
|
||||||
|
WRITER_TITLE: "Writer",
|
||||||
|
READER_TITLE: "Reader",
|
||||||
|
ADD_BUTTON: "Add Note",
|
||||||
|
REMOVE_BUTTON: "Remove",
|
||||||
|
BACK_LINK: "Back to Home",
|
||||||
|
NOTE_PLACEHOLDER: "Enter a note...",
|
||||||
|
EMPTY_NOTES: "There are no notes to display.",
|
||||||
|
STORED_AT: "Stored at: ",
|
||||||
|
UPDATED_AT: "Updated at: ",
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="notes-page">
|
||||||
|
<header class="page-header">
|
||||||
|
<h1 id="page-title"></h1>
|
||||||
|
<p id="status" class="status" aria-live="polite"></p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section id="notes" class="notes reader-notes" aria-label="Notes"></section>
|
||||||
|
|
||||||
|
<a id="back-link" class="back-link" href="./index.html"></a>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type="module" src="./js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="notes-page">
|
||||||
|
<header class="page-header">
|
||||||
|
<h1 id="page-title"></h1>
|
||||||
|
<p id="status" class="status" aria-live="polite"></p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section id="notes" class="notes" aria-label="Notes"></section>
|
||||||
|
<button id="add-button" class="add-button" type="button"></button>
|
||||||
|
|
||||||
|
<a id="back-link" class="back-link" href="./index.html"></a>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type="module" src="./js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user