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

1 import {getById, writeError, writeDebug, writeInfo, h } 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 if (upgradeCallback) 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 try {
43 db?.close();
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 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", {
74 onClick: async () => {
75 if (await askForConfirmation(`Are you sure you want to delete store '${storeName}'?`)) {
76 InitDB((event) => {
77 writeDebug(`Deleting store: ${storeName}, version: ${event.newVersion}`);
78 event.target.result.deleteObjectStore(storeName);
79 writeDebug("Deleted store: " + storeName);
80 });
81
82 if (openButton.closeCallback) openButton.closeCallback();
83 }
84 }
85 }, "Delete");
86
87 const openButton = h("button", {
88 onClick: () => {
89 if (openButton.textContent === "Open") {
90 openButton.closeCallback = openStore(storeName, deleteButton);
91 openButton.textContent = "Close";
92 } else {
93 openButton.closeCallback();
94 openButton.textContent = "Open";
95 openButton.closeCallback = null;
96 }
97 }
98 }, "Open");
99
100 const storeElement = h("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 = h("fieldset", {class: "panel"}, [h("legend", [storeName + " ", addButton])]);
112 const closeStore = () => {
113 openedStores.removeChild(storeElement);
114 };
115
116 openedStores.appendChild(storeElement);
117 const elementList = h("div", {class:"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", {
135 onClick: async () => {
136 const value = await getInput("Edit value", "new value");
137
138 if (value) {
139 const transaction = db.transaction(storeName, 'readwrite');
140 transaction.oncomplete = () => {
141 showItems();
142 }
143 const store = transaction.objectStore(storeName);
144 store.put(value, key);
145 }
146 }
147 }, "Edit");
148
149 const deleteItemButton = h("button", {
150 onClick: () => {
151 const transaction = db.transaction(storeName, 'readwrite');
152 transaction.oncomplete = () => {
153 showItems();
154 }
155 const store = transaction.objectStore(storeName);
156 store.delete(key);
157 }
158 }, "Delete");
159
160 newChildren.push(h("span", value));
161 newChildren.push(editItemButton);
162 newChildren.push(deleteItemButton);
163
164 cursor.continue();
165 }
166 }
167
168 showItems();
169
170 addButton.addEventListener('click', async () => {
171 const value = await getInput("New value", "value");
172 if (value) {
173 const transaction = db.transaction(storeName, "readwrite");
174 transaction.oncomplete = () => {
175 showItems();
176 }
177 const store = transaction.objectStore(storeName);
178 store.add(value);
179 }
180 })
181
182 return closeStore;
183 }
184
185 /**
186 * @param {string} title
187 * @param {string} placeholder
188 * @return {Promise<string>}
189 */
190 function getInput(title, placeholder) {
191 return new Promise((resolve) => {
192 const inputDialog = getById("inputDialog");
193 const cancelInputButton = getById('cancelInputButton');
194 const inputDialogInput = getById('inputDialogInput');
195 const inputForm = getById('inputForm');
196
197 inputDialogInput.placeholder = placeholder;
198 getById("inputTitle").textContent = title;
199
200 inputDialog.showModal();
201
202 const cancelCallback = () => {
203 inputDialog.close("cancelInput");
204 cancelInputButton.removeEventListener("click", cancelCallback);
205 inputForm.removeEventListener("submit", submitCallback);
206 resolve(null);
207 };
208
209 cancelInputButton.addEventListener('click', cancelCallback);
210
211 const submitCallback = () => {
212 const inputValue = inputDialogInput.value;
213 inputDialogInput.value = '';
214 inputForm.removeEventListener("submit", submitCallback);
215 cancelInputButton.removeEventListener("click", cancelCallback);
216 resolve(inputValue);
217 };
218
219 inputForm.addEventListener('submit', submitCallback);
220 })
221 }
222
223 function askForConfirmation(question) {
224 return new Promise((resolve) => {
225 const confirmDialog = getById("confirmDialog");
226 getById("confirmMessage").textContent = question;
227 confirmDialog.showModal();
228
229 const cancelConfirmButton = getById("cancelConfirmButton");
230 const closeCancelled = () => {
231 confirmDialog.close("cancelConfirmation");
232 resolve(false)
233 };
234 cancelConfirmButton.addEventListener('click', closeCancelled);
235
236 const confirmForm = getById("confirmForm");
237 const submitConfirmation = () => {
238 cancelConfirmButton.removeEventListener('click', closeCancelled);
239 confirmForm.removeEventListener('submit', submitConfirmation);
240 resolve(true);
241 };
242 confirmForm.addEventListener('submit', submitConfirmation);
243 })
244 }