Blog/Components/Pages/Storage.razor.js 7.9 K · 242 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 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 }