diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 2bc116381..1c1306947 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -247,8 +247,8 @@ importers: specifier: ^2.12.0 version: 2.13.0 svelte-ux: - specifier: ^0.62.10 - version: 0.62.12(@babel/core@7.24.4)(postcss@8.4.38)(svelte@4.2.17) + specifier: ^0.66.4 + version: 0.66.4(@babel/core@7.24.4)(postcss@8.4.38)(svelte@4.2.17) type-fest: specifier: ^4.18.2 version: 4.18.2 @@ -1234,8 +1234,8 @@ packages: tslib: 2.6.2 dev: false - /@fortawesome/fontawesome-common-types@6.5.1: - resolution: {integrity: sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==} + /@fortawesome/fontawesome-common-types@6.5.2: + resolution: {integrity: sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==} engines: {node: '>=6'} requiresBuild: true dev: false @@ -4783,8 +4783,8 @@ packages: engines: {node: '>=0.8'} dev: true - /clsx@2.1.0: - resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} + /clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} dev: false @@ -6141,8 +6141,8 @@ packages: engines: {node: '>= 4'} dev: true - /immer@10.0.4: - resolution: {integrity: sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==} + /immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} dev: false /immutable@3.7.6: @@ -8959,8 +8959,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /sveld@0.19.1(@babel/core@7.24.4)(postcss@8.4.38): - resolution: {integrity: sha512-ec4fRABGafRNASMdvyztLLlkjLBvBdlI7p3egd9HtIai1ThPwdu7eNn044ywKtzjtjRy0JIvaucHCfGt1YyP8Q==} + /sveld@0.20.0(@babel/core@7.24.4)(postcss@8.4.38): + resolution: {integrity: sha512-PQRs0evdOjsFzjHuPu8B/flqbJX2osN2D0++6nl77jhwriQazaEF0pkQAMwPRaSn/kMivvUdWkkPKi1NmHLosg==} hasBin: true dependencies: '@rollup/plugin-node-resolve': 13.3.0(rollup@2.79.1) @@ -9143,27 +9143,27 @@ packages: turnstile-types: 1.2.0 dev: true - /svelte-ux@0.62.12(@babel/core@7.24.4)(postcss@8.4.38)(svelte@4.2.17): - resolution: {integrity: sha512-lkcEEulsqjtJS2a9Vs8aPMqA/vBfSgjnEnkCYl2glbJTjfcfc9pnj6VkrOYvlUHntJF5m6MMKqQTMwe9P7w7kA==} + /svelte-ux@0.66.4(@babel/core@7.24.4)(postcss@8.4.38)(svelte@4.2.17): + resolution: {integrity: sha512-p5imk/FbnDdYPPK35X7RPdc5hLtG83RQlyGudul39TjiaVxp66rnqZT6AKXh+2h4CUVyH1chojohjg1jXRos8w==} peerDependencies: - svelte: ^3.56.0 || ^4.0.0 + svelte: ^3.56.0 || ^4.0.0 || ^5.0.0-next.120 dependencies: '@floating-ui/dom': 1.6.5 - '@fortawesome/fontawesome-common-types': 6.5.1 + '@fortawesome/fontawesome-common-types': 6.5.2 '@mdi/js': 7.4.47 - clsx: 2.1.0 + clsx: 2.1.1 culori: 4.0.1 d3-array: 3.2.4 d3-scale: 4.0.2 date-fns: 3.6.0 - immer: 10.0.4 + immer: 10.1.1 lodash-es: 4.17.21 prism-svelte: 0.5.0 prism-themes: 1.9.0 prismjs: 1.29.0 - sveld: 0.19.1(@babel/core@7.24.4)(postcss@8.4.38) + sveld: 0.20.0(@babel/core@7.24.4)(postcss@8.4.38) svelte: 4.2.17 - tailwind-merge: 2.2.2 + tailwind-merge: 2.3.0 zod: 3.23.8 transitivePeerDependencies: - '@babel/core' @@ -9230,8 +9230,8 @@ packages: tslib: 2.6.2 dev: true - /tailwind-merge@2.2.2: - resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==} + /tailwind-merge@2.3.0: + resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} dependencies: '@babel/runtime': 7.24.1 dev: false diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte index fd6ee9ff5..da5c6175b 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte @@ -1,17 +1,18 @@ {#if service} {#key service} - + {/key} {/if} diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.ts b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.ts index 617aeb10a..e28227b3e 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.ts +++ b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.ts @@ -1,5 +1,33 @@ -import type {PageLoadEvent} from './$types'; +import { getClient, graphql } from '$lib/gql'; + +import type {PageLoadEvent} from './$types'; +import { error } from '@sveltejs/kit'; +import { tryMakeNonNullable } from '$lib/util/store'; + export const ssr = false; // 💖 -export function load(event: PageLoadEvent) { - return {code: event.params.project_code}; +export async function load(event: PageLoadEvent) { + const client = getClient(); + const projectCode = event.params.project_code; + const projectResult = await client + .awaitedQueryStore(event.fetch, + graphql(` + query viewerProject($projectCode: String!) { + projectByCode(code: $projectCode) { + id + name + code + } + } + `), + { projectCode } + ); + + const nonNullableProject = tryMakeNonNullable(projectResult.projectByCode); + if (!nonNullableProject) { + error(404); + } + + return { + project: nonNullableProject, + }; } diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts index 64dc0c4ca..771e1a6cd 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts +++ b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts @@ -1,18 +1,23 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { type IEntry, type IExampleSentence, type ISense, type JsonPatch, - type LexboxApi, type QueryOptions, type WritingSystems, type WritingSystemType, - type WritingSystem + type WritingSystem, + type LexboxApiClient, + type LexboxApiFeatures } from 'viewer/lexbox-api'; -export class LfClassicLexboxApi implements LexboxApi { +export class LfClassicLexboxApi implements LexboxApiClient { constructor(private projectCode: string) { } + SupportedFeatures(): LexboxApiFeatures { + return {}; + } + async GetWritingSystems(): Promise { const result = await fetch(`/api/lfclassic/${this.projectCode}/writingSystems`); return (await result.json()) as WritingSystems; diff --git a/frontend/viewer/index.html b/frontend/viewer/index.html index 31f15202f..5b0aa95bb 100644 --- a/frontend/viewer/index.html +++ b/frontend/viewer/index.html @@ -15,7 +15,7 @@
- +
diff --git a/frontend/viewer/package.json b/frontend/viewer/package.json index 1f481a89d..75798fdc3 100644 --- a/frontend/viewer/package.json +++ b/frontend/viewer/package.json @@ -41,7 +41,7 @@ "postcss": "^8.4.38", "svelte-preprocess": "^5.1.4", "svelte-routing": "^2.12.0", - "svelte-ux": "^0.62.10", + "svelte-ux": "^0.66.4", "type-fest": "^4.18.2" } } diff --git a/frontend/viewer/src/CrdtProjectView.svelte b/frontend/viewer/src/CrdtProjectView.svelte index a58c6bdb1..a7f0d8b81 100644 --- a/frontend/viewer/src/CrdtProjectView.svelte +++ b/frontend/viewer/src/CrdtProjectView.svelte @@ -18,4 +18,4 @@ SetupSignalR(connection); let connected = false; - + diff --git a/frontend/viewer/src/ProjectView.svelte b/frontend/viewer/src/ProjectView.svelte index 5118b3ee3..1375450d4 100644 --- a/frontend/viewer/src/ProjectView.svelte +++ b/frontend/viewer/src/ProjectView.svelte @@ -7,10 +7,10 @@ import {useLexboxApi} from './lib/services/service-provider'; import type {IEntry} from './lib/mini-lcm'; import {setContext} from 'svelte'; - import {derived, writable} from 'svelte/store'; + import {derived, writable, type Readable} from 'svelte/store'; import {deriveAsync} from './lib/utils/time'; - import type {ViewConfig} from './lib/config-types'; - import ViewOptions from './lib/layout/ViewOptions.svelte'; + import {type ViewConfig, type LexboxPermissions, type ViewOptions, type LexboxFeatures} from './lib/config-types'; + import ViewOptionsDrawer from './lib/layout/ViewOptionsDrawer.svelte'; import EntryList from './lib/layout/EntryList.svelte'; import Toc from './lib/layout/Toc.svelte'; import {fade} from 'svelte/transition'; @@ -21,21 +21,36 @@ export let loading = false; - const viewConfig = writable({ - generateExternalChanges: false, + const lexboxApi = useLexboxApi(); + const features = writable(lexboxApi.SupportedFeatures()); + setContext>('features', features); + + const permissions = writable({ + write: true, + comment: true, + }); + + const options = writable({ showExtraFields: false, hideEmptyFields: false, activeView: views[0], - readonly: undefined, + generateExternalChanges: false, }); - setContext('viewConfig', derived(viewConfig, (config) => ({ - ...config, - hideEmptyFields: config.hideEmptyFields || config.readonly, - }))); + const viewConfig = derived([options, permissions, features], ([config, permissions, features]) => { + const readonly = !permissions.write || !features.write; + return { + ...config, + readonly, + hideEmptyFields: config.hideEmptyFields || readonly, + }; + }); + + setContext>('viewConfig', viewConfig); + + export let projectName: string; export let isConnected: boolean; $: connected.set(isConnected); - const lexboxApi = useLexboxApi(); const connected = writable(false); const search = writable(''); @@ -122,7 +137,7 @@
- +
@@ -144,7 +159,9 @@ Configure
- + {#if $features.history} + + {/if} @@ -160,7 +177,7 @@ class="grid flex-grow items-start justify-stretch md:justify-center" style="grid-template-columns: minmax(0, min-content) minmax(0, min-content) minmax(0, min-content);" > -
+
pickedEntry = true} />
@@ -216,7 +233,7 @@
- + {/if}
diff --git a/frontend/viewer/src/TestProjectView.svelte b/frontend/viewer/src/TestProjectView.svelte index 9f47357b2..a982c6f48 100644 --- a/frontend/viewer/src/TestProjectView.svelte +++ b/frontend/viewer/src/TestProjectView.svelte @@ -1,8 +1,9 @@  - + diff --git a/frontend/viewer/src/WebComponent.svelte b/frontend/viewer/src/WebComponent.svelte index 44a05a6ca..eccd13cca 100644 --- a/frontend/viewer/src/WebComponent.svelte +++ b/frontend/viewer/src/WebComponent.svelte @@ -6,6 +6,8 @@ let loading = true; + export let projectName: string; + onMount(() => { const shadowRoot = document.querySelector('lexbox-svelte')?.shadowRoot; if (!shadowRoot) throw new Error('Could not find shadow root'); @@ -42,5 +44,5 @@
- +
diff --git a/frontend/viewer/src/app.d.ts b/frontend/viewer/src/app.d.ts new file mode 100644 index 000000000..a766261e2 --- /dev/null +++ b/frontend/viewer/src/app.d.ts @@ -0,0 +1,5 @@ +export { }; // for some reason this is required in order to make global changes + +declare global { + function enableDevMode(): void; +} diff --git a/frontend/viewer/src/lib/config-types.ts b/frontend/viewer/src/lib/config-types.ts index d74d21c8f..0ab11617b 100644 --- a/frontend/viewer/src/lib/config-types.ts +++ b/frontend/viewer/src/lib/config-types.ts @@ -1,6 +1,7 @@ import type { IEntry, IExampleSentence, IMultiString, ISense } from './mini-lcm'; import type { ConditionalKeys } from 'type-fest'; +import type { LexboxApiFeatures } from './services/lexbox-api'; import type { views } from './config-data'; export type WritingSystemType = 'vernacular' | 'analysis'; @@ -49,10 +50,20 @@ export type ViewConfigFieldProps = { extra?: true, }; -export type ViewConfig = { +export type LexboxFeatures = LexboxApiFeatures; + +export type LexboxPermissions = { + write: boolean, + comment: boolean, +} + +export type ViewOptions = { generateExternalChanges: boolean, showExtraFields: boolean, hideEmptyFields: boolean, activeView: typeof views[number], - readonly?: true, +} + +export type ViewConfig = ViewOptions & { + readonly?: boolean, } diff --git a/frontend/viewer/src/lib/entry-data.ts b/frontend/viewer/src/lib/entry-data.ts index 365b7c596..aedffd464 100644 --- a/frontend/viewer/src/lib/entry-data.ts +++ b/frontend/viewer/src/lib/entry-data.ts @@ -1,5 +1,7 @@ import type { IEntry, WritingSystems } from './mini-lcm'; +export const projectName = 'Sena 3'; + export const writingSystems: WritingSystems = { 'analysis': [ { diff --git a/frontend/viewer/src/lib/entry-editor/EntityListItemActions.svelte b/frontend/viewer/src/lib/entry-editor/EntityListItemActions.svelte index 71328e6c1..34bc3d83a 100644 --- a/frontend/viewer/src/lib/entry-editor/EntityListItemActions.svelte +++ b/frontend/viewer/src/lib/entry-editor/EntityListItemActions.svelte @@ -1,14 +1,18 @@ @@ -187,7 +188,9 @@ Delete Entry - + {#if $features.history} + + {/if} {/if} diff --git a/frontend/viewer/src/lib/in-memory-api-service.ts b/frontend/viewer/src/lib/in-memory-api-service.ts index 7279e9ffe..808c7088e 100644 --- a/frontend/viewer/src/lib/in-memory-api-service.ts +++ b/frontend/viewer/src/lib/in-memory-api-service.ts @@ -1,14 +1,15 @@ import type { + LexboxApiClient, IEntry, IExampleSentence, ISense, JsonPatch, - LexboxApi, + LexboxApiFeatures, QueryOptions, WritingSystemType, WritingSystems } from './services/lexbox-api'; -import {entries, writingSystems} from './entry-data'; +import {entries, projectName, writingSystems} from './entry-data'; import { type WritingSystem } from './mini-lcm'; import { headword } from './utils'; @@ -27,7 +28,13 @@ function filterEntries(entries: IEntry[], query: string) { ].some(value => value?.toLowerCase().includes(query.toLowerCase()))) } -export class InMemoryApiService implements LexboxApi { +export class InMemoryApiService implements LexboxApiClient { + + SupportedFeatures(): LexboxApiFeatures { + return {}; + } + + readonly projectName = projectName; private _entries = entries; private _Entries(): IEntry[] { diff --git a/frontend/viewer/src/lib/layout/DevContent.svelte b/frontend/viewer/src/lib/layout/DevContent.svelte new file mode 100644 index 000000000..b56750d24 --- /dev/null +++ b/frontend/viewer/src/lib/layout/DevContent.svelte @@ -0,0 +1,17 @@ + + +{#if $isDev} + +{/if} diff --git a/frontend/viewer/src/lib/layout/ViewOptions.svelte b/frontend/viewer/src/lib/layout/ViewOptions.svelte deleted file mode 100644 index 77af46695..000000000 --- a/frontend/viewer/src/lib/layout/ViewOptions.svelte +++ /dev/null @@ -1,58 +0,0 @@ - - - -
- ({ value: view, label: view.label }))} - bind:value={$viewConfig.activeView} - classes={{root: 'view-select w-auto'}} - clearable={false} - labelPlacement="top" - clearSearchOnOpen={false} - fieldActions={(elem) => /* a hack to disable typing/filtering */ {elem.readOnly = true; return [];}} - search={() => /* a hack to always show all options */ Promise.resolve()}> - - - - - -
-
- Debug - - - - -
-
-
- - diff --git a/frontend/viewer/src/lib/layout/ViewOptionsDrawer.svelte b/frontend/viewer/src/lib/layout/ViewOptionsDrawer.svelte new file mode 100644 index 000000000..f4ae8fb58 --- /dev/null +++ b/frontend/viewer/src/lib/layout/ViewOptionsDrawer.svelte @@ -0,0 +1,72 @@ + + + +
+ ({ value: view, label: view.label }))} + bind:value={$options.activeView} + classes={{root: 'view-select w-auto'}} + clearable={false} + labelPlacement="top" + clearSearchOnOpen={false} + fieldActions={(elem) => /* a hack to disable typing/filtering */ {elem.readOnly = true; return [];}} + search={() => /* a hack to always show all options */ Promise.resolve()}> + + + + {#if !$viewConfig.readonly} + + + {/if} +
+ +
+ Debug + + + + + + +
+
+
+
+ + diff --git a/frontend/viewer/src/lib/services/lexbox-api.ts b/frontend/viewer/src/lib/services/lexbox-api.ts index ed78feeb7..fc41d3b66 100644 --- a/frontend/viewer/src/lib/services/lexbox-api.ts +++ b/frontend/viewer/src/lib/services/lexbox-api.ts @@ -1,12 +1,21 @@ -export type { IEntry, IExampleSentence, ISense, QueryOptions, WritingSystem, WritingSystems } from '../mini-lcm'; import type {IEntry, IExampleSentence, ISense, QueryOptions, WritingSystem, WritingSystems} from '../mini-lcm'; + import type { Operation } from 'fast-json-patch'; + +export type { IEntry, IExampleSentence, ISense, QueryOptions, WritingSystem, WritingSystems } from '../mini-lcm'; + export type JsonPatch = Operation[]; export enum WritingSystemType { Vernacular = 0, Analysis = 1, } + +export interface LexboxApiFeatures { + history?: boolean; + write?: boolean; +}; + export interface LexboxApi { GetWritingSystems(): Promise; CreateWritingSystem(type: WritingSystemType, writingSystem: WritingSystem): Promise; @@ -28,3 +37,9 @@ export interface LexboxApi { UpdateExampleSentence(entryGuid: string, senseGuid: string, exampleSentenceGuid: string, update: JsonPatch): Promise; DeleteExampleSentence(entryGuid: string, senseGuid: string, exampleSentenceGuid: string): Promise; } + +export interface LexboxApiMetadata { + SupportedFeatures(): LexboxApiFeatures; +} + +export type LexboxApiClient = LexboxApi & LexboxApiMetadata; diff --git a/frontend/viewer/src/lib/services/service-provider-dotnet.ts b/frontend/viewer/src/lib/services/service-provider-dotnet.ts index 2e92f8cef..25c68371e 100644 --- a/frontend/viewer/src/lib/services/service-provider-dotnet.ts +++ b/frontend/viewer/src/lib/services/service-provider-dotnet.ts @@ -1,5 +1,6 @@ import type { DotNet } from '@microsoft/dotnet-js-interop'; -import { LexboxServiceProvider } from './service-provider'; +import type { LexboxApiClient, LexboxApiMetadata } from './lexbox-api'; +import { LexboxService } from './service-provider'; declare global { interface Lexbox { @@ -8,17 +9,23 @@ declare global { } export class DotNetServiceProvider { - static services: Record = {}; - - static setService(key: string, service: DotNet.DotNetObject) { + static setLexboxApi(service: DotNet.DotNetObject) { const dotNetProxy = new Proxy(service, { - get(target: DotNet.DotNetObject, prop: string,) { + get(target: DotNet.DotNetObject, prop: string) { + if (prop === 'SupportedFeatures' satisfies keyof LexboxApiMetadata) { + return () => { + return { + history: true, + write: true, + }; + } + } return function (...args: any[]) { return target.invokeMethodAsync(prop, ...args); }; }, - }); - LexboxServiceProvider.setService(key, dotNetProxy); + }) as unknown as LexboxApiClient; + globalThis.window.lexbox.ServiceProvider.setService(LexboxService.LexboxApi, dotNetProxy); } } diff --git a/frontend/viewer/src/lib/services/service-provider-signalr.ts b/frontend/viewer/src/lib/services/service-provider-signalr.ts index 04dbc9dd3..790270921 100644 --- a/frontend/viewer/src/lib/services/service-provider-signalr.ts +++ b/frontend/viewer/src/lib/services/service-provider-signalr.ts @@ -1,16 +1,26 @@ -import type { HubConnection } from '@microsoft/signalr'; import {getHubProxyFactory, getReceiverRegister} from '../generated-signalr-client/TypedSignalR.Client'; -import type { LexboxApi } from './lexbox-api'; -import {LexboxServices} from './service-provider'; + import type { Entry } from '../mini-lcm'; +import type { HubConnection } from '@microsoft/signalr'; +import type { LexboxApiFeatures, LexboxApiMetadata } from './lexbox-api'; +import {LexboxService} from './service-provider'; export function SetupSignalR(connection: HubConnection) { - const hubFactory = getHubProxyFactory('ILexboxApiHub'); - const hubProxy = hubFactory.createHubProxy(connection); - getReceiverRegister('ILexboxClient').register(connection, { - OnEntryUpdated: async (entry: Entry) => { - console.log('OnEntryUpdated', entry); - } - }); - window.lexbox.ServiceProvider.setService(LexboxServices.LexboxApi, hubProxy satisfies LexboxApi); + const hubFactory = getHubProxyFactory('ILexboxApiHub'); + const hubProxy = hubFactory.createHubProxy(connection); + + const lexboxApiHubProxy = Object.assign(hubProxy, { + SupportedFeatures(): LexboxApiFeatures { + return { + history: true, + write: true, + }; + } + } satisfies LexboxApiMetadata); + getReceiverRegister('ILexboxClient').register(connection, { + OnEntryUpdated: async (entry: Entry) => { + console.log('OnEntryUpdated', entry); + } + }); + window.lexbox.ServiceProvider.setService(LexboxService.LexboxApi, lexboxApiHubProxy); } diff --git a/frontend/viewer/src/lib/services/service-provider.ts b/frontend/viewer/src/lib/services/service-provider.ts index 2e5b149f8..bccafb31c 100644 --- a/frontend/viewer/src/lib/services/service-provider.ts +++ b/frontend/viewer/src/lib/services/service-provider.ts @@ -1,5 +1,4 @@ -import {InMemoryApiService} from '../in-memory-api-service'; -import type {LexboxApi} from './lexbox-api'; +import type {LexboxApiClient} from './lexbox-api'; declare global { @@ -12,28 +11,34 @@ declare global { } } -export enum LexboxServices { +export enum LexboxService { LexboxApi = 'LexboxApi', } -const SERVICE_KEYS = Object.values(LexboxServices); +type LexboxServiceRegistry = { + [LexboxService.LexboxApi]: LexboxApiClient, +}; + +const SERVICE_KEYS = Object.values(LexboxService); export class LexboxServiceProvider { - private services: Record = {}; + private services: LexboxServiceRegistry = {} as LexboxServiceRegistry; - public setService(key: string, service: unknown): void { + public setService(key: K, service: LexboxServiceRegistry[K]): void { this.validateServiceKey(key); this.services[key] = service; } - public getService(key: string): T { + public getService(key: LexboxService): LexboxServiceRegistry[K] { this.validateServiceKey(key); - return this.services[key] as T; + const service = this.services[key]; + if (!service) throw new Error(`Lexbox service '${key}' not found`); + return this.services[key]; } - private validateServiceKey(key: string): void { - if (!SERVICE_KEYS.includes(key as LexboxServices)) { - throw new Error(`Invalid service key: ${key}. Valid vales are: ${SERVICE_KEYS.join(', ')}`); + private validateServiceKey(key: LexboxService): void { + if (!SERVICE_KEYS.includes(key)) { + throw new Error(`Invalid service key: ${key}. Valid values are: ${SERVICE_KEYS.join(', ')}`); } } } @@ -42,10 +47,6 @@ if (!window.lexbox) { window.lexbox = {ServiceProvider: new LexboxServiceProvider()}; } else window.lexbox.ServiceProvider = new LexboxServiceProvider(); -export function useLexboxApi() { - let api = window.lexbox.ServiceProvider.getService(LexboxServices.LexboxApi); - if (!api) { - throw new Error('LexboxApi service not found'); - } - return api; +export function useLexboxApi(): LexboxApiClient { + return window.lexbox.ServiceProvider.getService(LexboxService.LexboxApi); }