Blog/Components/Pages/Storage.razor.js 8 K · 243 lines · raw · history

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 db = null;
45 InitDB(null);
46
47 const newStoreButton = getById('newStoreButton');
48 newStoreButton.addEventListener('click', async () => {
49 const newStoreName = await getInput("New store", "Store name");
50
51 if (newStoreName) {
52 writeDebug("New store submitted.");
53 InitDB((event) => {
54 writeDebug(`Creating new store: ${newStoreName}, version: ${event.newVersion}`);
55 event.target.result.createObjectStore(newStoreName, {autoIncrement: true});
56 writeDebug("New store created: " + newStoreName);
57 });
58 }
59 });
60
61 } catch (e) {
62 writeError(e.message);
63 throw e;
64 }
65 }
66
67 function showStores() {
68 const storeList = getById("storeList");
69 storeList.replaceChildren();
70
71 let storeNames = db.objectStoreNames;
72
73 for (const storeName of storeNames) {
74 const deleteButton = h("button", "Delete");
75
76 const openButton = h("button", "Open");
77 openButton.addEventListener('click', () => {
78 if (openButton.textContent === "Open") {
79 openButton.closeCallback = openStore(storeName, deleteButton);
80 openButton.textContent = "Close";
81 } else {
82 openButton.closeCallback();
83 openButton.textContent = "Open";
84 openButton.closeCallback = null;
85 }
86 })
87
88 deleteButton.addEventListener('click', async () => {
89 if (await askForConfirmation(`Are you sure you want to delete store '${storeName}'?`)) {
90 InitDB((event) => {
91 writeDebug(`Deleting store: ${storeName}, version: ${event.newVersion}`);
92 event.target.result.deleteObjectStore(storeName);
93 writeDebug("Deleted store: " + storeName);
94 });
95
96 if (openButton.closeCallback) openButton.closeCallback();
97 }
98 })
99
100 const storeElement = span(storeName);
101 storeList.appendChild(storeElement);
102 storeList.appendChild(openButton);
103 storeList.appendChild(deleteButton);
104 }
105 }
106
107 function openStore(storeName) {
108 const openedStores = getById("openedStores");
109
110 const addButton = h("button", "Add");
111 const storeElement = addClass(h("fieldset", h("legend", storeName + " ", addButton)), "docs");
112 const closeStore = () => {
113 openedStores.removeChild(storeElement);
114 };
115
116 openedStores.appendChild(storeElement);
117 const elementList = addClass(h("div"), "grid-stores");
118 storeElement.appendChild(elementList);
119
120 function showItems() {
121
122 /** @member {Node[]} newChildren*/
123 const newChildren = [];
124 const store = db.transaction(storeName).objectStore(storeName);
125 store.openCursor().onsuccess = (event) => {
126 /** @member {IDBCursorWithValue} cursor*/
127 const cursor = event.target.result;
128 if (!cursor) {
129 elementList.replaceChildren(...newChildren);
130 return;
131 }
132 const value = cursor.value;
133 const key = cursor.key;
134 const editItemButton = h("button", "Edit");
135
136 const deleteItemButton = h("button", "Delete");
137 deleteItemButton.addEventListener('click', () => {
138 const transaction = db.transaction(storeName, 'readwrite');
139 transaction.oncomplete = () => {
140 showItems();
141 }
142 const store = transaction.objectStore(storeName);
143 store.delete(key);
144 })
145
146 editItemButton.addEventListener('click', async () => {
147 const value = await getInput("Edit value", "new value");
148
149 if (value) {
150 const transaction = db.transaction(storeName, 'readwrite');
151 transaction.oncomplete = () => {
152 showItems();
153 }
154 const store = transaction.objectStore(storeName);
155 store.put(value, key);
156 }
157 })
158
159 newChildren.push(span(value));
160 newChildren.push(editItemButton);
161 newChildren.push(deleteItemButton);
162
163 cursor.continue();
164 }
165 }
166
167 showItems();
168
169 addButton.addEventListener('click', async () => {
170 const value = await getInput("New value", "value");
171 if (value) {
172 const transaction = db.transaction(storeName, "readwrite");
173 transaction.oncomplete = () => {
174 showItems();
175 }
176 const store = transaction.objectStore(storeName);
177 store.add(value);
178 }
179 })
180
181 return closeStore;
182 }
183
184 /**
185 * @param {string} title
186 * @param {string} placeholder
187 * @return {Promise<string>}
188 */
189 function getInput(title, placeholder) {
190 return new Promise((resolve) => {
191 const inputDialog = getById("inputDialog");
192 const cancelInputButton = getById('cancelInputButton');
193 const inputDialogInput = getById('inputDialogInput');
194 const inputForm = getById('inputForm');
195
196 inputDialogInput.placeholder = placeholder;
197 getById("inputTitle").textContent = title;
198
199 inputDialog.showModal();
200
201 const cancelCallback = () => {
202 inputDialog.close("cancelInput");
203 cancelInputButton.removeEventListener("click", cancelCallback);
204 inputForm.removeEventListener("submit", submitCallback);
205 resolve(null);
206 };
207
208 cancelInputButton.addEventListener('click', cancelCallback);
209
210 const submitCallback = () => {
211 const inputValue = inputDialogInput.value;
212 inputDialogInput.value = '';
213 inputForm.removeEventListener("submit", submitCallback);
214 cancelInputButton.removeEventListener("click", cancelCallback);
215 resolve(inputValue);
216 };
217
218 inputForm.addEventListener('submit', submitCallback);
219 })
220 }
221
222 function askForConfirmation(question) {
223 return new Promise((resolve) => {
224 const confirmDialog = getById("confirmDialog");
225 getById("confirmMessage").textContent = question;
226 confirmDialog.showModal();
227
228 const cancelConfirmButton = getById("cancelConfirmButton");
229 const closeCancelled = () => {
230 confirmDialog.close("cancelConfirmation");
231 resolve(false)
232 };
233 cancelConfirmButton.addEventListener('click', closeCancelled);
234
235 const confirmForm = getById("confirmForm");
236 const submitConfirmation = () => {
237 cancelConfirmButton.removeEventListener('click', closeCancelled);
238 confirmForm.removeEventListener('submit', submitConfirmation);
239 resolve(true);
240 };
241 confirmForm.addEventListener('submit', submitConfirmation);
242 })
243 }