import { css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { Task } from "@lit/task"; import type { PropertiesPicker, Resource, SearchObject } from "@src/tems"; import * as rdf from "@src/rdf"; import * as utils from "@helpers"; import "~icons/material-symbols/shopping-cart-outline"; import { msg } from "@lit/localize"; interface MenuTabItem { name?: string; route?: string; defaultDataSrc?: string; active: boolean; } @customElement("solid-tems-catalog") export class TemsCatalog extends utils.OrbitComponent { constructor() { super(); utils.setupCacheInvalidation(this, { keywords: ["groups", "objects"], }); } 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 = "Catalog"; @state() search: SearchObject[] = []; @state() view: "card" | "list" | "table" | "map" = "card"; @state() resultCount = this.objects?.length || 0; pairComponents: MenuTabItem[] = []; cherryPickedProperties: PropertiesPicker[] = [ { key: "@id", value: "@id" }, { key: "@type", value: "@type" }, { key: "activation_date", value: "activation_date", cast: utils.formatDate, }, { key: "activation_status", value: "activation_status" }, { key: "actual_representation", value: "actual_representation" }, { key: "address", value: "address" }, { key: "affiliation", value: "affiliation", expand: true }, { key: "ai", value: "ai" }, { key: "allow_ai", value: "allow_ai" }, { key: "assets", value: "assets", expand: true }, { key: "categories", value: "categories", expand: true }, { key: "city", value: "city" }, { key: "contact_url", value: "contact_url" }, { key: "contributors", value: "contributors" }, { key: "copyright", value: "copyright" }, { key: "country", value: "country" }, { key: "creator", value: "creator" }, { key: "data_offers", value: "data_offers", expand: false }, { key: "description", value: "description" }, { key: "developper", value: "developper", expand: true }, { key: "documentation_url", value: "documentation_url" }, { key: "editor", value: "editor" }, { key: "elevation", value: "elevation" }, { key: "file_size", value: "file_size" }, { key: "format", value: "format", expand: true }, { key: "iframe", value: "iframe" }, { key: "image", value: "image", expand: true }, { key: "images", value: "images", expand: true }, { key: "instruction", value: "instruction" }, { key: "is_api", value: "is_api" }, { key: "is_external", value: "is_external" }, { key: "is_in_app", value: "is_in_app" }, { key: "is_low_polygons", value: "is_low_polygons" }, { key: "keywords", value: "keywords", expand: true }, { key: "kind", value: "kind" }, { key: "language", value: "language", expand: true }, { key: "last_update", value: "last_update", cast: utils.formatDate }, { key: "latitude", value: "latitude" }, { key: "licence", value: "licence", expand: true }, { key: "licences", value: "licences", expand: true }, { key: "location", value: "location", expand: true }, { key: "long_description", value: "long_description" }, { key: "longitude", value: "longitude" }, { key: "name", value: "name" }, { key: "offers", value: "offers", expand: true }, { key: "original_languages", value: "original_languages" }, { key: "polygons", value: "polygons" }, { key: "prices", value: "prices" }, { key: "provider", value: "providers", expand: false }, { key: "providers", value: "providers", expand: false }, { key: "publication_date", value: "publication_date", cast: utils.formatDate, }, { key: "publication_service", value: "publication_service" }, { key: "release_date", value: "release_date", cast: utils.formatDate }, { key: "rights_holder", value: "rights_holder" }, { key: "services", value: "services", expand: false }, { key: "state", value: "state" }, { key: "texture_formats", value: "texture_formats" }, { key: "texture_resolution", value: "texture_resolution" }, { key: "texture", value: "texture" }, { key: "time_period", value: "time_period" }, { key: "title", value: "title" }, { key: "topics", value: "topics", expand: true }, { key: "type", value: "type", expand: true }, { key: "update_date", value: "update_date", cast: utils.formatDate }, { key: "url", value: "url" }, { key: "website", value: "website" }, { key: "year", value: "year" }, ]; async _responseAdaptator(response: Resource): Promise { // avoid infinite loop const limitedProperties = [ { key: "name", value: "name" }, { key: "url", value: "url" }, { key: "iframe", value: "iframe" }, { key: "description", value: "description" }, { key: "image", value: "image", expand: true, }, { key: "images", value: "image", expand: true, cast: (i: rdf.Image | rdf.Image[]) => (Array.isArray(i) ? i.filter((i) => !i.iframe) : [i])[0], }, { key: "offers", value: "offers", expand: true }, { key: "categories", value: "categories", expand: true }, ]; for (const prop of this.cherryPickedProperties.filter( (p) => p.expand === false )) { if (response[prop.value]) { response[prop.value] = Array.isArray(response[prop.value]) ? await Promise.all( response[prop.value].map(async (v: Resource) => this._getProxyValue(v["@id"], false, limitedProperties) ) ) : await this._getProxyValue( response[prop.value]["@id"], false, limitedProperties ); } } if (this.hasType(rdf.RDFTYPE_OBJECT, [response])) { response.owned = this.menuComponent?.user?.owned_objects?.some( (obj: Resource) => obj["@id"] === response["@id"] ); } if (response.images) { if (!Array.isArray(response.images)) { response.images = [response.images].filter((i) => i); } } if (response.providers) { if (!Array.isArray(response.providers)) { response.providers = [response.providers].filter((i) => i); } } if (response.data_offers) { if (!Array.isArray(response.data_offers)) { response.data_offers = [response.data_offers].filter((i) => i); } } if (response.services) { if (!Array.isArray(response.services)) { response.services = [response.services].filter((i) => i); } } if (response.licences) { if (!Array.isArray(response.licences)) { response.licences = [response.licences].filter((i) => i); } } if (response.keywords) { if (!Array.isArray(response.keywords)) { response.keywords = [response.keywords].filter((i) => i); } } return response; } async _afterAttach() { this.menuComponent = document.querySelector( `[uniq="${this.orbit?.getComponent("menu")?.uniq}"]` ); return Promise.resolve(); } _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; if (!this.menuComponent.ready) { await new Promise((resolve) => { this.menuComponent.addEventListener("user-ready", () => { resolve(true); }); }); } if (dataSrc === "federation") { if (!this.component.parameters.federation) { return; } const federationSrc = `store://local.${this.component.route}/`; const content = ( await Promise.all( this.component.parameters.federation.map( async (federation: string) => await this._getProxyValue(federation, true, []) ) ) ) .flat() .map((obj: Resource) => ({ "@id": obj["@id"], "@type": obj["@type"], })); await window.sibStore.setLocalData( { "@cache": "false", "@context": "https://cdn.startinblox.com/owl/context.jsonld", "@type": "ldp:Container", "@id": federationSrc, "ldp:contains": [...content], permissions: [{ mode: { "@type": "view" } }], }, federationSrc ); this.defaultDataSrc = federationSrc; if (this.route) utils.requestNavigation(this.route, this.defaultDataSrc); return; } this.displayFiltering = !this.component.parameters.disableFiltering; if (this.component.parameters.tabGroup) { this.pairComponents = this.orbit.components .filter( (component) => component.parameters?.tabGroup === this.component.parameters.tabGroup ) .map((component) => ({ name: component.parameters.tab, route: component.uniq, active: this.component.uniq === component.uniq, defaultDataSrc: component.uniq === this.component.uniq ? this.defaultDataSrc : component.parameters.defaultDataSrc === "federation" ? component.instance?.defaultDataSrc : component.parameters.defaultDataSrc, })) .filter((component) => component.route && component.name); } if (!this.hasCachedDatas || this.oldDataSrc !== dataSrc) { if (this.nestedField && this.nestedField === "owned_objects") { const resource = await window.sibStore.getData( this.menuComponent.user["@id"], this.context ); const nestedResource = await resource[this.nestedField]; const response = []; for (const entry of nestedResource) { const subResource = await window.sibStore.getData( entry["@id"], utils.CLIENT_CONTEXT ); const indirectContainer = await this._getProxyValue(subResource); response.push(indirectContainer); } this.datas = response.flat(); } else { if (!dataSrc) return; this.datas = await this._getProxyValue(dataSrc); } this.hasCachedDatas = true; } if (this.oldDataSrc !== dataSrc) { this.oldDataSrc = dataSrc; } this.object = this.datas.find((obj: Resource) => obj["@id"] === objSrc); return utils.sort(this.datas, "update_date", "desc"); }, 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; } _manageBookmark(e: Event) { e.preventDefault(); console.log(msg("Disabled for POC")); if (e.detail.add) { this._addToMyLibrary(e.detail.object); } else { this._removeFromMyLibrary(e.detail.object); } } async _addToMyLibrary(object: Resource) { const currentUser = this.menuComponent?.user; if (currentUser) { const owners = await object._originalResource?.owners; let userSet = await owners["user_set"]; if (!userSet) userSet = []; if (!Array.isArray(userSet)) userSet = [{ "@id": await userSet["@id"] }]; userSet.push({ "@id": currentUser["@id"] }); const currentRes = { "@context": this.object._originalResource.serverContext, user_set: userSet, }; await window.sibStore.patch(currentRes, owners["@id"]); currentUser.owned_objects.push(object); const ownedObjectContainer = await currentUser._originalResource[ "owned_objects" ]; for (const catalog of ownedObjectContainer) { await window.sibStore.clearCache(catalog["@id"]); await window.sibStore.getData(catalog["@id"], {}); document.dispatchEvent( new CustomEvent("save", { detail: { resource: { "@id": catalog["@id"] } }, bubbles: true, }) ); } document.dispatchEvent( new CustomEvent("save", { detail: { resource: { "@id": owners["@id"] } }, bubbles: true, }) ); } } async _removeFromMyLibrary(object: Resource) { const currentUser = this.menuComponent?.user; if (currentUser) { const owners = await object._originalResource?.owners; let userSet = await owners["user_set"]; if (!userSet) userSet = []; if (!Array.isArray(userSet)) userSet = [{ "@id": await userSet["@id"] }]; const updatedUserSet = userSet.filter( (user: Resource) => user["@id"] !== currentUser["@id"] ); const currentRes = { "@context": this.object._originalResource.serverContext, user_set: updatedUserSet, }; await window.sibStore.patch(currentRes, owners["@id"]); currentUser.owned_objects = currentUser.owned_objects.filter( (obj: Resource) => obj["@id"] !== object["@id"] ); const ownedObjectContainer = await currentUser._originalResource[ "owned_objects" ]; for (const catalog of ownedObjectContainer) { await window.sibStore.clearCache(catalog["@id"]); await window.sibStore.getData(catalog["@id"], {}); document.dispatchEvent( new CustomEvent("save", { detail: { resource: { "@id": catalog["@id"] } }, bubbles: true, }) ); } document.dispatchEvent( new CustomEvent("save", { detail: { resource: { "@id": owners["@id"] } }, bubbles: true, }) ); } } render() { return ( this.gatekeeper() || this._getResource.render({ pending: () => html``, complete: (datas) => { if (!datas) { return nothing; } return html` `} > ${this.pairComponents ? html` ${this.pairComponents.map( (component) => html`` )} ` : nothing} ${this.object ? html` ` : nothing} `; }, }) ); } }