import * as utils from "@helpers"; import { Task } from "@lit/task"; import type { Resource } from "@src/component"; import { OrbitDSPComponent, type TemsMenuTabItem, type TemsSearchObject, } from "@startinblox/solid-tems-shared"; import { css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; export interface DSPProviderConfig { name: string; address: string; color?: string; participantId?: string; } @customElement("solid-dsp-catalog") export class DSPCatalog extends OrbitDSPComponent { constructor() { super(); utils.setupCacheInvalidation(this, { keywords: ["groups", "objects", "dsp"], }); } static styles = css` .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 2, 49, 0.2); z-index: 9999; display: flex; justify-content: center; align-items: center; } `; @property({ attribute: "header", type: String }) header?: string = "TEMS DSP Catalog"; @property({ attribute: "show-provider-filter", type: Boolean }) showProviderFilter = false; @property({ attribute: "show-stats", type: Boolean }) showStats = false; @state() search: TemsSearchObject[] = []; @state() view: "card" | "list" | "table" | "map" = "card"; @state() resultCount = this.objects?.length || 0; async _responseAdaptator(response: Resource): Promise { if (response.images) { if (!Array.isArray(response.images)) { response.images = response.images["ldp:contains"]?.filter( (i: any) => i, ) || [response.images]; } } if (response.providers) { if (!Array.isArray(response.providers)) { response.providers = [response.providers].filter((i: any) => i); } } else if (response.provider) { response.providers = [response.provider].filter((i: any) => i); } // Map DSP provider metadata to standard format if (response._provider) { response.providers = [ { name: response._provider, "@id": response._providerAddress, color: response._providerColor, }, ]; } if (response.categories) { if (!Array.isArray(response.categories)) { response.categories = response.categories["ldp:contains"]?.filter( (i: any) => i, ) || [response.categories]; } } // Map DSP dataset fields to TEMS format if (response["dcterms:title"] && !response.name) { response.name = response["dcterms:title"]; } if (response["dcterms:description"] && !response.description) { response.description = response["dcterms:description"]; } return response; } _getResource = new Task(this, { task: async ([dataSrc, objSrc]) => { if ( (!dataSrc && !objSrc) || !this.orbit || (!this.noRouter && this.route && this.currentRoute && !this.route.startsWith(this.currentRoute)) ) return; // Initialize providers from component parameters if not already set if ( (!this.providers || this.providers.length === 0) && this.component?.parameters?.providers ) { this.providers = this.component.parameters.providers; } this.displayFiltering = !this.component.parameters.disableFiltering; if (this.component.parameters.tabGroup) { this.pairComponents = this.orbit.components .filter( (component: any) => component.parameters?.tabGroup === this.component.parameters.tabGroup, ) .map((component: any) => { const comp = { name: component.parameters.tab, route: component.uniq, active: this.component.uniq === component.uniq, defaultDataSrc: component.attributes?.["default-data-src"] || component.parameters.defaultDataSrc, }; return comp; }) .filter((component: any) => component.route && component.name); } if (!this.hasCachedDatas || this.oldDataSrc !== dataSrc) { if (!dataSrc) return; // Fetch from DSP connectors const datasets = (await this.fetchFederatedCatalog()) as Resource[]; if (datasets !== undefined && Array.isArray(datasets)) { // Process each dataset through the response adaptator this.datas = await Promise.all( datasets.map( async (dataset) => await this._responseAdaptator(dataset), ), ); this.hasCachedDatas = true; } } if (this.oldDataSrc !== dataSrc) { this.oldDataSrc = dataSrc; } if (!Array.isArray(this.datas)) { this.datas = []; } this.object = this.datas.find((obj: Resource) => obj["@id"] === objSrc); return this.datas; }, args: () => [ this.defaultDataSrc, this.dataSrc, this.caching, this.currentRoute, ], }); _search(e: Event) { e.preventDefault(); this.search = e.detail; this.filterCount = this.search.filter((s) => s.name !== "search").length; } _toggleChangeView(e: Event) { e.preventDefault(); this.view = e.detail; } _openModal(e: Event) { e.preventDefault(); if (this.route) { if ("use-id" in (this.component.routeAttributes || {})) { utils.requestNavigation(this.route, e.detail["@id"]); } else { const rdfType = e.detail["@type"]?.at(-1) ?? e.detail["@type"]; if (rdfType) { const compatibleComponents = window.orbit?.components?.filter( (c) => c?.routeAttributes?.["rdf-type"] === rdfType, ); if (compatibleComponents?.[0]?.route) { utils.requestNavigation( compatibleComponents[0]?.route, e.detail["@id"], ); } } } } } _closeModal(e: Event) { e.preventDefault(); if (this.route) utils.requestNavigation(this.route, this.defaultDataSrc); } _closeModalFromBackground(e: Event) { e.preventDefault(); if (this.route && e.target?.classList.contains("modal")) utils.requestNavigation(this.route, this.defaultDataSrc); } _resultCountUpdate(e: Event) { this.resultCount = e.detail ?? 0; } pairComponents: TemsMenuTabItem[] = []; render() { return ( this.gatekeeper() || this._getResource.render({ pending: () => html``, complete: (_) => { if (!this.datas) { return nothing; } return html` ${ this.pairComponents ? html`
${this.pairComponents.map( (component: any) => html``, )}
` : nothing }
${ this.showStats && this.providers && this.providers.length > 0 ? html`

Provider Statistics

${this.providers.map((provider: DSPProviderConfig) => { const providerDatasets = this.datas.filter( (d: Resource) => d._provider === provider.name, ); return html`
${provider.name}
${providerDatasets.length} datasets
`; })}
` : nothing } ${ this.object ? html`` : nothing }
`; }, }) ); } }