From b60d68e12ee03fcd75e53c3d05df8e4250554434 Mon Sep 17 00:00:00 2001 From: Dominic Kempf Date: Mon, 15 Jul 2024 17:23:25 +0200 Subject: [PATCH] Attempt at making on demand instance data queries --- .gitignore | 3 + index.html | 1 + src/App.svelte | 9 ++- src/components/EasyDBDetailView.svelte | 18 +++-- src/generate.js | 96 +++----------------------- src/lib/apiaccess.js | 5 +- src/lib/easydbData.js | 92 ++++++++++++++++++++++++ src/lib/easydbHelpers.js | 11 +-- src/lib/easydbPregen.js | 4 ++ src/lib/stores.js | 43 +++++++++++- 10 files changed, 181 insertions(+), 101 deletions(-) create mode 100644 src/lib/easydbData.js create mode 100644 src/lib/easydbPregen.js diff --git a/.gitignore b/.gitignore index a547bf3..88266cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# This file is regularyl overwritten for pregeneration +src/lib/easydb.js + # Logs logs *.log diff --git a/index.html b/index.html index 6e3676f..4e9fb70 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + EasyDB Detail View Demo diff --git a/src/App.svelte b/src/App.svelte index 76f0cb8..ad90597 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -3,6 +3,7 @@ import EasyDbDetailView from "./components/EasyDBDetailView.svelte"; let uuid = "859e2318-32f6-4013-8468-ef8cec0b581b"; + let instance = "https://heidicon.ub.uni-heidelberg.de"; let languages = [ { value: 'de-DE', name: 'Deutsch'}, @@ -21,6 +22,12 @@ explain what can be interactively controlled from the JavaScript side. On the right hand side, we see the detail view component result.

+
+ + +
- +
diff --git a/src/components/EasyDBDetailView.svelte b/src/components/EasyDBDetailView.svelte index 4cb6fd8..68b9cf5 100644 --- a/src/components/EasyDBDetailView.svelte +++ b/src/components/EasyDBDetailView.svelte @@ -1,20 +1,28 @@ -{#await easydb_api_object(uuid) } - Waiting for API response... -{:then data } - +{#await $easydbDataPromiseStore } + Accessing the EasyDB instance... +{:then} + {#await easydb_api_object(uuid) } + Waiting for API response... + {:then data } + + {/await} {/await} diff --git a/src/generate.js b/src/generate.js index b37774b..9890c99 100644 --- a/src/generate.js +++ b/src/generate.js @@ -1,7 +1,5 @@ import { writeFile } from 'fs/promises'; - -// TODO: In the reorganization functions below we could also cull data that we don't need -// in order to reduce the bundle size. This is a low priority performance optimization. +import { accessInstance } from './lib/easydbData.js'; // This "trick" is super-unfortunate, but Svelte Preprocessing is fully regex-based // and any attempts to pass stringified data that contains backslashes will result in @@ -11,96 +9,18 @@ function escape(str) { return str.replaceAll("\\\"", "__ESCAPED_QUOTES__").replaceAll("\\", "__BACKSLASH__"); } -// We massage the API response a bit to fit our needs better. This makes it more convenient -// to work with the data in the Svelte component. -function reorganize_masks(maskdata) { - let newdata = {}; - for (let mask of maskdata.masks) { - newdata[mask.name] = mask; - } - return newdata; -} - -// We do the same for the schema data, where we want to access the tables directly by -// their name in the Svelte component. -function reorganize_schemas(schemadata) { - let newdata = {}; - for (let table of schemadata.tables) { - newdata[table.name] = table; - console.log(table.name); - } - return newdata; -} - -// Get a session token from the EasyDB instance -const token_response = await fetch('https://heidicon.ub.uni-heidelberg.de/api/session'); -if(token_response.status != 200) { - throw new Error("Could not get a session token from the EasyDB instance."); -} -const token_json = await token_response.json(); -const token = token_json.token; - -// Authenticcate the session token -const auth_response = await fetch( - 'https://heidicon.ub.uni-heidelberg.de/api/session/authenticate?' + - new URLSearchParams({ - token: token, - method: 'anonymous', - }), - { - method: 'POST', - } -); -if(auth_response.status != 200) { - throw new Error("Could not authenticate the session token."); -} - -// Fetch all the masks for this instance -const mask_response = await fetch( - 'https://heidicon.ub.uni-heidelberg.de/api/v1/mask/HEAD?' + - new URLSearchParams({ - "token": token, - "format": "json", - }), -); -if(mask_response.status != 200) { - throw new Error("Could not fetch the masks for this instance"); -} -const masks = await mask_response.json(); - -// Fetch all the l10n data for this instance -const l10n_response = await fetch( - 'https://heidicon.ub.uni-heidelberg.de/api/v1/l10n/user/HEAD?' + - new URLSearchParams({ - "token": token, - }), -); -if(l10n_response.status != 200) { - throw new Error("Could not fetch the l10n data for this instance"); -} -const l10n = await l10n_response.json(); - -// Fetch the schema data for this instance -const schema_response = await fetch( - 'https://heidicon.ub.uni-heidelberg.de/api/v1/schema/user/HEAD?' + - new URLSearchParams({ - "token": token, - "format": "json", - }), -); -if(schema_response.status != 200) { - throw new Error("Could not fetch the schema data for this instance"); -} -const schema = await schema_response.json(); +const instance = "https://heidicon.ub.uni-heidelberg.de"; +const data = await accessInstance(instance); // Inject the data into the Svelte component as a constant const content = - `export const masks = JSON.parse('${escape(JSON.stringify(reorganize_masks(masks)))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` + - `export const l10n = JSON.parse('${escape(JSON.stringify(l10n))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` + - `export const schemas = JSON.parse('${escape(JSON.stringify(reorganize_schemas(schema)))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n`; + `export const pregen_instance = '${instance}';\n` + + `export const pregen_masks = JSON.parse('${escape(JSON.stringify(data.masks))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` + + `export const pregen_l10n = JSON.parse('${escape(JSON.stringify(data.l10n))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n` + + `export const pregen_schemas = JSON.parse('${escape(JSON.stringify(data.schemas))}'.replaceAll("__ESCAPED_QUOTES__", "\\\\\\"").replaceAll("__BACKSLASH__", "\\\\"));\n`; try { - await writeFile('src/lib/easydb.js', content); + await writeFile('src/lib/easydbPregen.js', content); console.log('Successfully regenerated the EasyDB data.'); } catch (error) { console.error('Error writing the EasyDB data:', error); diff --git a/src/lib/apiaccess.js b/src/lib/apiaccess.js index acc60d7..bc86440 100644 --- a/src/lib/apiaccess.js +++ b/src/lib/apiaccess.js @@ -1,10 +1,13 @@ +import { get } from 'svelte/store'; +import { easydbInstanceStore } from './stores'; + export async function easydb_api_object(uuid) { if (!uuid) { return {} } // Fetch the schema data for this instance - const response = await fetch('http://localhost:8080/https://heidicon.ub.uni-heidelberg.de/api/objects/uuid/' + uuid); + const response = await fetch(`${get(easydbInstanceStore)}/api/objects/uuid/${uuid}`); if(response.status != 200) { throw new Error(`Could not fetch the data for uuid: ${uuid}`); } diff --git a/src/lib/easydbData.js b/src/lib/easydbData.js new file mode 100644 index 0000000..395ee75 --- /dev/null +++ b/src/lib/easydbData.js @@ -0,0 +1,92 @@ +// TODO: In the reorganization functions below we could also cull data that we don't need +// in order to reduce the bundle size. This is a low priority performance optimization. + +// We massage the API response a bit to fit our needs better. This makes it more convenient +// to work with the data in the Svelte component. +function reorganize_masks(maskdata) { + let newdata = {}; + for (let mask of maskdata.masks) { + newdata[mask.name] = mask; + } + return newdata; +} + +// We do the same for the schema data, where we want to access the tables directly by +// their name in the Svelte component. +function reorganize_schemas(schemadata) { + let newdata = {}; + for (let table of schemadata.tables) { + newdata[table.name] = table; + } + return newdata; +} + +export async function accessInstance(instance) { + // Get a session token from the EasyDB instance + const token_response = await fetch(`${instance}/api/session`); + if(token_response.status != 200) { + throw new Error("Could not get a session token from the EasyDB instance."); + } + const token_json = await token_response.json(); + const token = token_json.token; + + // Authenticcate the session token + const auth_response = await fetch( + `${instance}/api/session/authenticate?` + + new URLSearchParams({ + token: token, + method: 'anonymous', + }), + { + method: 'POST', + } + ); + if(auth_response.status != 200) { + throw new Error("Could not authenticate the session token."); + } + + // Fetch all the masks for this instance + const mask_response = await fetch( + `${instance}/api/v1/mask/HEAD?` + + new URLSearchParams({ + "token": token, + "format": "json", + }), + ); + if(mask_response.status != 200) { + throw new Error("Could not fetch the masks for this instance"); + } + const masks = await mask_response.json(); + + // Fetch all the l10n data for this instance + const l10n_response = await fetch( + `${instance}/api/v1/l10n/user/HEAD?` + + new URLSearchParams({ + "token": token, + }), + ); + if(l10n_response.status != 200) { + throw new Error("Could not fetch the l10n data for this instance"); + } + const l10n = await l10n_response.json(); + + // Fetch the schema data for this instance + const schema_response = await fetch( + `${instance}/api/v1/schema/user/HEAD?` + + new URLSearchParams({ + "token": token, + "format": "json", + }), + ); + if(schema_response.status != 200) { + throw new Error("Could not fetch the schema data for this instance"); + } + const schema = await schema_response.json(); + + return { + instance: instance, + masks: reorganize_masks(masks), + l10n: l10n, + schemas: reorganize_schemas(schema), + } +} \ No newline at end of file diff --git a/src/lib/easydbHelpers.js b/src/lib/easydbHelpers.js index 775eb6c..436bb3d 100644 --- a/src/lib/easydbHelpers.js +++ b/src/lib/easydbHelpers.js @@ -1,18 +1,19 @@ -import { l10n, masks, schemas } from './easydb'; +import { easydbDataStore } from './stores'; +import { get } from 'svelte/store'; import { bestLanguage } from './l10n'; export function maskObj(data) { - return masks[data._mask]; + return get(easydbDataStore).masks[data._mask]; } // Given a data JSON object and a field from a mask, return the schema for that column export function findSchemaColumn(table, field) { - return schemas[table].columns.find((column) => column.name === field.column_name_hint); + return get(easydbDataStore).schemas[table].columns.find((column) => column.name === field.column_name_hint); } // Given a data JSON object and the field definition from a mask, return the label of the field with the language code lang export function fieldLabel(table, field, lang) { - return bestLanguage(l10n[`schema.${table}.column.${field.column_name_hint}`], lang); + return bestLanguage(get(easydbDataStore).l10n[`schema.${table}.column.${field.column_name_hint}`], lang); } // Given a data JSON object and the field definition from a mask, return a boolean whether it exists in the data @@ -48,7 +49,7 @@ export function reverseLinkedSubData(data, table, field) { } export function splitterTitle(data, table, options, lang) { - return bestLanguage(l10n[`mask.${schemas[table].table_id}.${maskObj(data).name}.splitter.${String(options.splitterIdx)}`], lang); + return bestLanguage(get(easydbDataStore).l10n[`mask.${get(easydbDataStore).schemas[table].table_id}.${maskObj(data).name}.splitter.${String(options.splitterIdx)}`], lang); } export function hasContent(data, table, fields) { diff --git a/src/lib/easydbPregen.js b/src/lib/easydbPregen.js new file mode 100644 index 0000000..b733d0a --- /dev/null +++ b/src/lib/easydbPregen.js @@ -0,0 +1,4 @@ +export const pregen_instance = null; +export const pregen_masks = null; +export const pregen_l10n = null; +export const pregen_schemas = null; diff --git a/src/lib/stores.js b/src/lib/stores.js index ca80010..9966ad2 100644 --- a/src/lib/stores.js +++ b/src/lib/stores.js @@ -1,3 +1,44 @@ -import { writable } from "svelte/store"; +import { derived, writable } from "svelte/store"; +import { accessInstance } from "./easydbData"; +import { pregen_instance, pregen_l10n, pregen_masks, pregen_schemas } from "./easydbPregen"; +// A derived store that resolves a promise +function derivedPromise(store) { + return derived(store, ($store, set) => { + $store.then(value => { + set(value); + }); + }); +} + +// Our pregenerated defaults wrapped in a Promise +async function pregenDefaults() { + return { + instance: pregen_instance, + masks: pregen_masks, + schemas: pregen_schemas, + l10n: pregen_l10n, + }; +} + +// This manages the global state of the current language export const l10nStore = writable(null); + +// This manages the global state of the EasyDB instance we are talking to +export const easydbInstanceStore = writable(null); + +// A derived store that delivers a promise. +export const easydbDataPromiseStore = derived( + easydbInstanceStore, + ($instance) => { + if ($instance === pregen_instance) { + return pregenDefaults(); + } + return accessInstance($instance); + } +); + +// A derived store that awaits above promise. It would be better to not even +// expose the promise store, but I did not get this to work without the explicit +// await in EasyDBDetailView.svelte. +export const easydbDataStore = derivedPromise(easydbDataPromiseStore);