add storage

author
Marijn Besseling <njirambem@gmail.com> · 2026-01-20 21:09 UTC
commit
3b398ae7127106473dfba8483804dabe0db7e0e3
parent
7087a8d6ee
tree
browse at this commit

4 files changed +315 -0

Blog/Components/App.razor +1 -0

@@ -54,6 +54,7 @@
54 54 <li><NavLink href="/qrcodeScan">QR-code scanner</NavLink></li>
55 55 <li><NavLink href="/qrcodeGenerator">QR-code generator</NavLink></li>
56 56 <li><NavLink href="/BRPTestData">BRP Test Data</NavLink></li>
57 + <li><NavLink href="/Storage">Storage</NavLink></li>
57 58 <li><NavLink href="https://git.bes.is">Git</NavLink></li>
58 59 <!-- <li><a href="/webrtc.html">WebRTC</a></li> -->
59 60 <!-- <li><a href="/spotify/index.html">Spotify</a></li> -->

Blog/Components/Pages/Storage.razor +47 -0

@@ -0,0 +1,47 @@
1 +@page "/Storage"
2 +<PageTitle>Storage</PageTitle>
3 +<PageScript Src="./Components/Pages/Storage.razor.js"></PageScript>
4 +
5 +<main>
6 + <fieldset class="docs">
7 + <legend>Stores <button id="newStoreButton">New</button></legend>
8 + <div id="storeList" class="grid-stores">
9 +
10 + </div>
11 + </fieldset>
12 +
13 + <dialog id="inputDialog">
14 + <fieldset class="docs">
15 + <legend id="inputTitle"></legend>
16 + <form method="dialog" id="inputForm">
17 + <p>
18 + <input id="inputDialogInput" type="text" />
19 + </p>
20 + <div>
21 + <button type="submit">Confirm</button>
22 + <button id="cancelInputButton" type="reset">Cancel</button>
23 + </div>
24 + </form>
25 + </fieldset>
26 + </dialog>
27 +
28 + <dialog id="confirmDialog">
29 + <fieldset class="docs">
30 + <legend>Are you sure?</legend>
31 + <form method="dialog" id="confirmForm">
32 + <p id="confirmMessage">
33 +
34 + </p>
35 + <div>
36 + <button type="submit">Confirm</button>
37 + <button id="cancelConfirmButton" type="reset">Cancel</button>
38 + </div>
39 + </form>
40 + </fieldset>
41 + </dialog>
42 +
43 + <div id="openedStores">
44 +
45 + </div>
46 + <Log/>
47 +</main>
No newline at end of file

Blog/Components/Pages/Storage.razor.js +242 -0

@@ -0,0 +1,242 @@
1 +import {getById, writeError, addClass, writeDebug, writeInfo, h, span} from "/common.module.js";
2 +
3 +/** @member {IDBDatabase} db */
4 +let db;
5 +
6 +function InitDB(upgradeCallback) {
7 + writeDebug(`db = ${db?.version}`)
8 + if (db) {
9 + writeDebug("Closing database");
10 + db.close();
11 + }
12 + writeDebug(`db = ${db?.version}`)
13 + const DBOpenRequest = db === null || db === undefined
14 + ? window.indexedDB.open('mb-storage')
15 + : window.indexedDB.open('mb-storage', db.version + 1);
16 +
17 + DBOpenRequest.onupgradeneeded = (e) => {
18 + writeDebug(`Upgrading from ${e.oldVersion} to ${e.newVersion}`);
19 + upgradeCallback(e);
20 + };
21 +
22 + DBOpenRequest.onerror = () => {
23 + writeError('Error loading database.');
24 + };
25 +
26 + DBOpenRequest.onsuccess = () => {
27 + db = DBOpenRequest.result;
28 + writeDebug(`db = ${db.version}`)
29 + writeDebug("Database opened successfully.");
30 + db.onversionchange = () => {
31 + writeInfo(`Version changed in another tab, closing database.`);
32 + }
33 + showStores();
34 + };
35 +
36 + DBOpenRequest.onblocked = () => {
37 + writeError(`Please close all other tabs with this site open!`);
38 + }
39 +
40 +}
41 +
42 +export function onLoad() {
43 + try {
44 + InitDB(null);
45 +
46 + const newStoreButton = getById('newStoreButton');
47 + newStoreButton.addEventListener('click', async () => {
48 + const newStoreName = await getInput("New store", "Store name");
49 +
50 + if (newStoreName) {
51 + writeDebug("New store submitted.");
52 + InitDB((event) => {
53 + writeDebug(`Creating new store: ${newStoreName}, version: ${event.newVersion}`);
54 + event.target.result.createObjectStore(newStoreName, {autoIncrement: true});
55 + writeDebug("New store created: " + newStoreName);
56 + });
57 + }
58 + });
59 +
60 + } catch (e) {
61 + writeError(e.message);
62 + throw e;
63 + }
64 +}
65 +
66 +function showStores() {
67 + const storeList = getById("storeList");
68 + storeList.replaceChildren();
69 +
70 + let storeNames = db.objectStoreNames;
71 +
72 + for (const storeName of storeNames) {
73 + const deleteButton = h("button", "Delete");
74 +
75 + const openButton = h("button", "Open");
76 + openButton.addEventListener('click', () => {
77 + if (openButton.textContent === "Open") {
78 + openButton.closeCallback = openStore(storeName, deleteButton);
79 + openButton.textContent = "Close";
80 + } else {
81 + openButton.closeCallback();
82 + openButton.textContent = "Open";
83 + openButton.closeCallback = null;
84 + }
85 + })
86 +
87 + deleteButton.addEventListener('click', async () => {
88 + if (await askForConfirmation(`Are you sure you want to delete store '${storeName}'?`)) {
89 + InitDB((event) => {
90 + writeDebug(`Deleting store: ${storeName}, version: ${event.newVersion}`);
91 + event.target.result.deleteObjectStore(storeName);
92 + writeDebug("Deleted store: " + storeName);
93 + });
94 +
95 + if (openButton.closeCallback) openButton.closeCallback();
96 + }
97 + })
98 +
99 + const storeElement = span(storeName);
100 + storeList.appendChild(storeElement);
101 + storeList.appendChild(openButton);
102 + storeList.appendChild(deleteButton);
103 + }
104 +}
105 +
106 +function openStore(storeName) {
107 + const openedStores = getById("openedStores");
108 +
109 + const addButton = h("button", "Add");
110 + const storeElement = addClass(h("fieldset", h("legend", storeName + " ", addButton)), "docs");
111 + const closeStore = () => {
112 + openedStores.removeChild(storeElement);
113 + };
114 +
115 + openedStores.appendChild(storeElement);
116 + const elementList = addClass(h("div"), "grid-stores");
117 + storeElement.appendChild(elementList);
118 +
119 + function showItems() {
120 +
121 + /** @member {Node[]} newChildren*/
122 + const newChildren = [];
123 + const store = db.transaction(storeName).objectStore(storeName);
124 + store.openCursor().onsuccess = (event) => {
125 + /** @member {IDBCursorWithValue} cursor*/
126 + const cursor = event.target.result;
127 + if (!cursor) {
128 + elementList.replaceChildren(...newChildren);
129 + return;
130 + }
131 + const value = cursor.value;
132 + const key = cursor.key;
133 + const editItemButton = h("button", "Edit");
134 +
135 + const deleteItemButton = h("button", "Delete");
136 + deleteItemButton.addEventListener('click', () => {
137 + const transaction = db.transaction(storeName, 'readwrite');
138 + transaction.oncomplete = () => {
139 + showItems();
140 + }
141 + const store = transaction.objectStore(storeName);
142 + store.delete(key);
143 + })
144 +
145 + editItemButton.addEventListener('click', async () => {
146 + const value = await getInput("Edit value", "new value");
147 +
148 + if (value) {
149 + const transaction = db.transaction(storeName, 'readwrite');
150 + transaction.oncomplete = () => {
151 + showItems();
152 + }
153 + const store = transaction.objectStore(storeName);
154 + store.put(value, key);
155 + }
156 + })
157 +
158 + newChildren.push(span(value));
159 + newChildren.push(editItemButton);
160 + newChildren.push(deleteItemButton);
161 +
162 + cursor.continue();
163 + }
164 + }
165 +
166 + showItems();
167 +
168 + addButton.addEventListener('click', async () => {
169 + const value = await getInput("New value", "value");
170 + if (value) {
171 + const transaction = db.transaction(storeName, "readwrite");
172 + transaction.oncomplete = () => {
173 + showItems();
174 + }
175 + const store = transaction.objectStore(storeName);
176 + store.add(value);
177 + }
178 + })
179 +
180 + return closeStore;
181 +}
182 +
183 +/**
184 + * @param {string} title
185 + * @param {string} placeholder
186 + * @return {Promise<string>}
187 + */
188 +function getInput(title, placeholder) {
189 + return new Promise((resolve) => {
190 + const inputDialog = getById("inputDialog");
191 + const cancelInputButton = getById('cancelInputButton');
192 + const inputDialogInput = getById('inputDialogInput');
193 + const inputForm = getById('inputForm');
194 +
195 + inputDialogInput.placeholder = placeholder;
196 + getById("inputTitle").textContent = title;
197 +
198 + inputDialog.showModal();
199 +
200 + const cancelCallback = () => {
201 + inputDialog.close("cancelInput");
202 + cancelInputButton.removeEventListener("click", cancelCallback);
203 + inputForm.removeEventListener("submit", submitCallback);
204 + resolve(null);
205 + };
206 +
207 + cancelInputButton.addEventListener('click', cancelCallback);
208 +
209 + const submitCallback = () => {
210 + const inputValue = inputDialogInput.value;
211 + inputDialogInput.value = '';
212 + inputForm.removeEventListener("submit", submitCallback);
213 + cancelInputButton.removeEventListener("click", cancelCallback);
214 + resolve(inputValue);
215 + };
216 +
217 + inputForm.addEventListener('submit', submitCallback);
218 + })
219 +}
220 +
221 +function askForConfirmation(question) {
222 + return new Promise((resolve) => {
223 + const confirmDialog = getById("confirmDialog");
224 + getById("confirmMessage").textContent = question;
225 + confirmDialog.showModal();
226 +
227 + const cancelConfirmButton = getById("cancelConfirmButton");
228 + const closeCancelled = () => {
229 + confirmDialog.close("cancelConfirmation");
230 + resolve(false)
231 + };
232 + cancelConfirmButton.addEventListener('click', closeCancelled);
233 +
234 + const confirmForm = getById("confirmForm");
235 + const submitConfirmation = () => {
236 + cancelConfirmButton.removeEventListener('click', closeCancelled);
237 + confirmForm.removeEventListener('submit', submitConfirmation);
238 + resolve(true);
239 + };
240 + confirmForm.addEventListener('submit', submitConfirmation);
241 + })
242 +}
No newline at end of file

Blog/wwwroot/app.css +25 -0

@@ -320,3 +320,28 @@ li.dash {
320 320 display: inline-block;
321 321 }
322 322
323 +dialog::backdrop {
324 + backdrop-filter: blur(calc(var(--s1) / 20));
325 +}
326 +
327 +dialog {
328 + background-color: transparent;
329 + color: var(--color);
330 + border: none;
331 + padding: 0;
332 +}
333 +
334 +dialog > fieldset {
335 + background-color: var(--background);
336 +}
337 +
338 +dialog > fieldset > legend {
339 + background-color: var(--background);
340 + border-radius: 1rem;
341 +}
342 +
343 +.grid-stores {
344 + display: grid;
345 + grid-gap: 1em;
346 + grid-template-columns: 1fr 0.4fr 0.4fr;
347 +}
No newline at end of file