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