feat: add browser cache for data

fix search name
main
Xubai Wang 2022-02-22 23:20:20 +08:00
parent 004977e6e4
commit e4ccc5cf50
2 changed files with 117 additions and 18 deletions

View File

@ -6,6 +6,9 @@
import { SITE_ROOT } from "./site-root.js"; import { SITE_ROOT } from "./site-root.js";
const CACHE_DB_NAME = "declaration-data";
const CACHE_DB_VERSION = 1;
/** /**
* The DeclarationDataCenter is used for declaration searching. * The DeclarationDataCenter is used for declaration searching.
* *
@ -40,26 +43,46 @@ export class DeclarationDataCenter {
*/ */
static async init() { static async init() {
if (!DeclarationDataCenter.singleton) { if (!DeclarationDataCenter.singleton) {
const timestampUrl = new URL(
`${SITE_ROOT}declaration-data.timestamp`,
window.location
);
const dataUrl = new URL( const dataUrl = new URL(
`${SITE_ROOT}declaration-data.bmp`, `${SITE_ROOT}declaration-data.bmp`,
window.location window.location
); );
const response = await fetch(dataUrl);
const json = await response.json(); const timestampRes = await fetch(timestampUrl);
// the data is a map of name (original case) to declaration data. const timestamp = await timestampRes.text();
const data = new Map(
json.map(({ name, doc, link, source: sourceLink }) => [ // try to use cache first
name, let store = await getDeclarationStore();
{ const data = await fetchCachedDeclarationData(store, timestamp);
if (data) {
// if data is defined, use the cached one.
DeclarationDataCenter.singleton = new DeclarationDataCenter(data);
} else {
// undefined. then fetch the data from the server.
const dataRes = await fetch(dataUrl);
const dataJson = await dataRes.json();
// the data is a map of name (original case) to declaration data.
const data = new Map(
dataJson.map(({ name, doc, link, source: sourceLink }) => [
name, name,
lowerName: name.toLowerCase(), {
lowerDoc: doc.toLowerCase(), name,
link, lowerName: name.toLowerCase(),
sourceLink, lowerDoc: doc.toLowerCase(),
}, link,
]) sourceLink,
); },
DeclarationDataCenter.singleton = new DeclarationDataCenter(data); ])
);
// get store again in case it's inactive
let store = await getDeclarationStore();
await cacheDeclarationData(store, timestamp, data);
DeclarationDataCenter.singleton = new DeclarationDataCenter(data);
}
} }
return DeclarationDataCenter.singleton; return DeclarationDataCenter.singleton;
} }
@ -136,3 +159,79 @@ function getMatches(declarations, pattern, maxResults = 30) {
} }
return results.sort(({ err: a }, { err: b }) => a - b).slice(0, maxResults); return results.sort(({ err: a }, { err: b }) => a - b).slice(0, maxResults);
} }
// TODO: refactor the indexedDB part to be more robust
/**
* Get the indexedDB database, automatically initialized.
* @returns {Promise<IDBObjectStore>}
*/
function getDeclarationStore() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(CACHE_DB_NAME, CACHE_DB_VERSION);
request.onerror = function (event) {
reject(
new Error(
`fail to open indexedDB ${CACHE_DB_NAME} of version ${CACHE_DB_VERSION}`
)
);
};
request.onupgradeneeded = function (event) {
let db = event.target.result;
// We only need to store one object, so no key path or increment is needed.
let objectStore = db.createObjectStore("declaration");
objectStore.transaction.oncomplete = function (event) {
resolve(objectStore);
};
};
request.onsuccess = function (event) {
resolve(
event.target.result
.transaction("declaration", "readwrite")
.objectStore("declaration")
);
};
});
}
/**
* Store data in indexedDB object store.
* @param {IDBObjectStore} store
* @param {string} timestamp
* @param {Map<string, any>} data
*/
function cacheDeclarationData(store, timestamp, data) {
return new Promise((resolve, reject) => {
let clearRequest = store.clear();
clearRequest.onsuccess = function (event) {
let addRequest = store.add(data, timestamp);
addRequest.onsuccess = function (event) {
resolve();
};
addRequest.onerror = function (event) {
reject(new Error(`fail to store declaration data`));
};
};
clearRequest.onerror = function (event) {
reject(new Error("fail to clear object store"));
};
});
}
/**
* Retrieve data from indexedDB database.
* @param {IDBObjectStore} store
* @param {string} timestamp
* @returns {Promise<Map<string, any>|undefined>}
*/
async function fetchCachedDeclarationData(store, timestamp) {
return new Promise((resolve, reject) => {
let transactionRequest = store.get(timestamp);
transactionRequest.onsuccess = function (event) {
resolve(event.result);
};
transactionRequest.onerror = function (event) {
reject(new Error(`fail to store declaration data`));
};
});
}

View File

@ -94,10 +94,10 @@ SEARCH_INPUT.addEventListener("input", async (ev) => {
// update search results // update search results
removeAllChildren(sr); removeAllChildren(sr);
for (const { decl, link } of result) { for (const { name, link } of result) {
const d = sr.appendChild(document.createElement("a")); const d = sr.appendChild(document.createElement("a"));
d.innerText = decl; d.innerText = name;
d.title = decl; d.title = name;
d.href = link; d.href = link;
} }
sr.setAttribute("state", "done"); sr.setAttribute("state", "done");