Implement indexedDB backed store making app offline friendly

This commit is contained in:
Adrian Gruntkowski 2024-11-15 23:00:18 +01:00
parent 274ef4c217
commit 599004206b
4 changed files with 156 additions and 26 deletions

View file

@ -1,6 +1,5 @@
Only immediate next things to do are listed here, without any far-fetching plans. Only immediate next things to do are listed here, without any far-fetching plans.
- Implement syncing local storage with service worker (either via postMessage or using IndexedDB)
- Implement reminders - Implement reminders
- Implement masonry layout - Implement masonry layout
- Implement color coding - Implement color coding

139
js/db-store.js Normal file
View file

@ -0,0 +1,139 @@
export class DBNoteStore extends EventTarget {
constructor(dbName, storeName) {
super();
this.dbName = dbName;
this.storeName = storeName;
this.db = null;
}
async all() {
const that = this;
return this.#connect().then(
(db) =>
new Promise((resolve, reject) => {
db
.transaction([that.storeName], "readonly")
.objectStore(that.storeName)
.getAll().onsuccess = (data) => resolve(data.target.result);
}),
);
}
async get(id) {
const that = this;
let result;
return this.#connect().then(
(db) =>
new Promise(
(resolve, reject) =>
(db
.transaction([that.storeName], "readonly")
.objectStore(that.storeName)
.get(id).onsuccess = (data) => resolve(data.target.result)),
),
);
}
async add(note) {
const that = this;
const entry = {
id: "id_" + Date.now(),
type: note.type,
content: note.content,
created: new Date(),
};
return this.#connect().then(
(db) =>
new Promise(
(resolve, reject) =>
(db
.transaction([that.storeName], "readwrite")
.objectStore(that.storeName)
.add(entry).onsuccess = () => resolve(null)),
),
);
}
async reset() {
const that = this;
return this.#connect().then(
(db) =>
new Promise(
(resolve, reject) =>
(db
.transaction([that.storeName], "readwrite")
.objectStore(that.storeName)
.clear().onsuccess = () => resolve(null)),
),
);
}
async remove({ id }) {
const that = this;
return this.#connect().then(
(db) =>
new Promise(
(resolve, reject) =>
(db
.transaction([that.storeName], "readwrite")
.objectStore(that.storeName)
.delete(id).onsuccess = () => resolve(null)),
),
);
}
async update(note) {
const that = this;
return this.#connect().then(
(db) =>
new Promise(
(resolve, reject) =>
(db
.transaction([that.storeName], "readwrite")
.objectStore(that.storeName)
.put(note).onsuccess = () => resolve(null)),
),
);
}
saveStorage() {
this.dispatchEvent(new CustomEvent("save"));
}
async #connect() {
if (!this.db) {
this.db = await this.#dbConnect();
}
return this.db;
}
#dbConnect() {
const that = this;
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onsuccess = (e) => {
resolve(e.target.result);
};
request.onerror = (e) => {
console.error(`indexedDB error: ${e.target.errorCode}`);
};
request.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore(that.storeName, {
keyPath: "id",
});
};
});
}
}

View file

@ -1,9 +1,14 @@
import "./service-worker.js"; import "./service-worker.js";
import { renderText } from "./dom.js"; import { renderText } from "./dom.js";
import { NoteStore } from "./store.js"; import { NoteStore } from "./store.js";
import { DBNoteStore } from "./db-store.js";
import "./components.js"; import "./components.js";
const Notes = new NoteStore("jenot-app"); const urlParams = new URLSearchParams(window.location.search);
const Notes = urlParams.has("localStorage")
? new NoteStore("jenot-app")
: new DBNoteStore("jenot-app", "notes");
const newNote = document.querySelector("#new-note"); const newNote = document.querySelector("#new-note");
const editNote = document.querySelector("#edit-note"); const editNote = document.querySelector("#edit-note");
@ -12,28 +17,27 @@ Notes.addEventListener("save", render.bind(this));
render(); render();
newNote.addEventListener("addNote", (e) => { newNote.addEventListener("addNote", async (e) => {
console.log(e.detail); await Notes.add(e.detail);
Notes.add(e.detail);
Notes.saveStorage(); Notes.saveStorage();
}); });
editNote.addEventListener("updateNote", (e) => { editNote.addEventListener("updateNote", async (e) => {
newNote.classList.remove("hidden"); newNote.classList.remove("hidden");
editNote.classList.add("hidden"); editNote.classList.add("hidden");
Notes.update(e.detail); await Notes.update(e.detail);
Notes.saveStorage(); Notes.saveStorage();
}); });
editNote.addEventListener("deleteNote", (e) => { editNote.addEventListener("deleteNote", async (e) => {
newNote.classList.remove("hidden"); newNote.classList.remove("hidden");
editNote.classList.add("hidden"); editNote.classList.add("hidden");
Notes.remove(e.detail); await Notes.remove(e.detail);
Notes.saveStorage(); Notes.saveStorage();
}); });
function render() { async function render() {
const notes = Notes.all(); const notes = await Notes.all();
const notesContainer = document.querySelector("#notes"); const notesContainer = document.querySelector("#notes");
notesContainer.replaceChildren(); notesContainer.replaceChildren();
@ -64,10 +68,11 @@ function render() {
notesContainer.appendChild(container); notesContainer.appendChild(container);
container.addEventListener("click", (e) => { container.addEventListener("click", async (e) => {
newNote.classList.add("hidden"); newNote.classList.add("hidden");
editNote.classList.remove("hidden"); editNote.classList.remove("hidden");
editNote.load(Notes.get(container.id)); const note = await Notes.get(container.id);
editNote.load(note);
}); });
}); });
} }

View file

@ -1,7 +1,6 @@
export class NoteStore extends EventTarget { export class NoteStore extends EventTarget {
localStorageKey; localStorageKey;
notes = []; notes = [];
editedNoteId = "none";
/* /*
Note structure: Note structure:
@ -31,7 +30,6 @@ export class NoteStore extends EventTarget {
all = () => this.notes; all = () => this.notes;
get = (id) => this.notes.find((note) => note.id === id); get = (id) => this.notes.find((note) => note.id === id);
getEditedNoteId = () => this.editedNoteId;
add(note) { add(note) {
this.notes.unshift({ this.notes.unshift({
@ -54,19 +52,11 @@ export class NoteStore extends EventTarget {
this.notes = this.notes.map((n) => (n.id === note.id ? note : n)); this.notes = this.notes.map((n) => (n.id === note.id ? note : n));
} }
setEditedNoteId(id) {
this.editedNoteId = id;
}
saveStorage() { saveStorage() {
window.localStorage.setItem( window.localStorage.setItem(
this.localStorageKey + "_notes", this.localStorageKey + "_notes",
JSON.stringify(this.notes), JSON.stringify(this.notes),
); );
window.localStorage.setItem(
this.localStorageKey + "_editedNoteId",
this.editedNoteId,
);
this.dispatchEvent(new CustomEvent("save")); this.dispatchEvent(new CustomEvent("save"));
} }
@ -74,8 +64,5 @@ export class NoteStore extends EventTarget {
this.notes = JSON.parse( this.notes = JSON.parse(
window.localStorage.getItem(this.localStorageKey + "_notes") || "[]", window.localStorage.getItem(this.localStorageKey + "_notes") || "[]",
); );
this.editedNoteId =
window.localStorage.getItem(this.localStorageKey + "_editedNoteId") ||
"none";
} }
} }